@storagehub-sdk/msp-client 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.node.js +1 -1
- package/dist/index.node.js.map +3 -3
- package/dist/modules/auth.d.ts +48 -11
- package/dist/modules/files.d.ts +4 -3
- package/dist/types.d.ts +12 -19
- package/package.json +2 -2
package/dist/index.browser.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{HttpClient as
|
|
1
|
+
import{HttpClient as _}from"@storagehub-sdk/core";import{getAddress as M}from"viem";var l=class{ctx;sessionProvider;constructor(e,t){this.ctx=e,this.sessionProvider=t}async withAuth(e){let n=(await this.sessionProvider())?.token;return n?e?{...e,Authorization:`Bearer ${n}`}:{Authorization:`Bearer ${n}`}:e}normalizePath(e){return e.replace(/^\/+|\/{2,}/g,(t,n)=>n===0?"":"/")}};var T=10,H=100,b=class extends l{getNonce(e,t,n,i,r){return this.ctx.http.post("/auth/nonce",{body:{address:e,chainId:t,domain:n,uri:i},headers:{"Content-Type":"application/json"},...r?{signal:r}:{}})}async verify(e,t,n){return await this.ctx.http.post("/auth/verify",{body:{message:e,signature:t},headers:{"Content-Type":"application/json"},...n?{signal:n}:{}})}async SIWE(e,t,n,i=T,r){let s=e.account,o=typeof s=="string"?s:s?.address;if(!o||!s)throw new Error("Wallet client has no active account; set wallet.account before calling SIWE");let c=M(o),p=await e.getChainId(),{message:u}=await this.getNonce(c,p,t,n,r),y=await e.signMessage({account:s,message:u}),h;for(let m=0;m<i;m++)try{return await this.verify(u,y,r)}catch(f){h=f,await this.delay(H)}throw h instanceof Error?h:new Error("SIWE verification failed")}async delay(e){await new Promise(t=>setTimeout(t,e))}async getProfile(e){let t=await this.withAuth();return this.ctx.http.get("/auth/profile",{...t?{headers:t}:{},...e?{signal:e}:{}})}};import{ensure0xPrefix as d,parseDate as E}from"@storagehub-sdk/core";function R(a){return a.type==="file"?{name:a.name,type:a.type,sizeBytes:a.sizeBytes,fileKey:d(a.fileKey),status:a.status,uploadedAt:E(a.uploadedAt)}:{name:a.name,type:a.type,children:(a.children??[]).map(R)}}var S=class extends l{async listBuckets(e){let t=await this.withAuth();return(await this.ctx.http.get("/buckets",{...t?{headers:t}:{},...e?{signal:e}:{}})).map(i=>({bucketId:d(i.bucketId),name:i.name,root:d(i.root),isPublic:i.isPublic,sizeBytes:i.sizeBytes,valuePropId:d(i.valuePropId),fileCount:i.fileCount}))}async getBucket(e,t){let n=await this.withAuth(),i=`/buckets/${encodeURIComponent(e)}`,r=await this.ctx.http.get(i,{...n?{headers:n}:{},...t?{signal:t}:{}});return{bucketId:d(r.bucketId),name:r.name,root:d(r.root),isPublic:r.isPublic,sizeBytes:r.sizeBytes,valuePropId:d(r.valuePropId),fileCount:r.fileCount}}async getFiles(e,t){let n=await this.withAuth(),i=`/buckets/${encodeURIComponent(e)}/files`,r=await this.ctx.http.get(i,{...n?{headers:n}:{},...t?.signal?{signal:t.signal}:{},...t?.path?{query:{path:this.normalizePath(t.path)}}:{}}),o=("files"in r?r.files:[r.tree]).map(R),c=o[0];return{bucketId:d(r.bucketId),files:o,...c?{tree:c}:{}}}};import{ensure0xPrefix as g,FileMetadata as W,FileTrie as z,hexToBytes as P,initWasm as x,parseDate as L}from"@storagehub-sdk/core";var F=class extends l{async getFileInfo(e,t,n){let i=await this.withAuth(),r=`/buckets/${encodeURIComponent(e)}/info/${encodeURIComponent(t)}`,s=await this.ctx.http.get(r,{...i?{headers:i}:{},...n?{signal:n}:{}});return{fileKey:g(s.fileKey),fingerprint:g(s.fingerprint),bucketId:g(s.bucketId),location:s.location,size:BigInt(s.size),isPublic:s.isPublic,uploadedAt:L(s.uploadedAt),status:s.status,blockHash:g(s.blockHash),...s.txHash?{txHash:g(s.txHash)}:{}}}async uploadFile(e,t,n,i,r,s){await x();let o=`/buckets/${encodeURIComponent(e)}/upload/${encodeURIComponent(t)}`,c=await this.withAuth(),p=await this.coerceToFormPart(n),u=p.size,y=await this.computeFileFingerprint(p),h=await this.formFileMetadata(i,e,r,y,BigInt(u)),m=await this.computeFileKey(h),f=P(t);if(m.length!==f.length||!m.every((U,v)=>U===f[v]))throw new Error(`Computed file key ${m.toString()} does not match provided file key ${f.toString()}`);let C=h.encode(),w=new FormData,k=new Blob([new Uint8Array(C)],{type:"application/octet-stream"});return w.append("file_metadata",k,"file_metadata"),w.append("file",p,"file"),await this.ctx.http.put(o,c?{body:w,headers:c}:{body:w})}async downloadFile(e,t){let n=`/download/${encodeURIComponent(e)}`,i={Accept:"*/*"};if(t?.range){let{start:s,end:o}=t.range,c=`bytes=${s}-${o??""}`;i.Range=c}let r=await this.withAuth(i);try{let s=await this.ctx.http.getRaw(n,{...r?{headers:r}:{},...t?.signal?{signal:t.signal}:{}});if(!s.body)throw new Error("Response body is null - unable to create stream");let o=s.headers.get("content-type"),c=s.headers.get("content-range"),p=s.headers.get("content-length"),u=p!==null?Number(p):void 0,y=typeof u=="number"&&Number.isFinite(u)?u:null;return{stream:s.body,status:s.status,contentType:o,contentRange:c,contentLength:y}}catch(s){if(this.isHttpError(s))return{stream:this.createEmptyStream(),status:s.status,contentType:null,contentRange:null,contentLength:null};throw s}}isHttpError(e){return e!==null&&typeof e=="object"&&"status"in e&&typeof e.status=="number"}createEmptyStream(){return new ReadableStream({start(e){e.close()}})}async coerceToFormPart(e){if(typeof Blob<"u"&&e instanceof Blob)return e;if(e instanceof Uint8Array)return new Blob([e.buffer]);if(typeof ArrayBuffer<"u"&&e instanceof ArrayBuffer)return new Blob([e]);if(e instanceof ReadableStream){let t=e.getReader(),n=[],i=0;try{for(;;){let{done:o,value:c}=await t.read();if(o)break;c&&(n.push(c),i+=c.length)}}finally{t.releaseLock()}let r=new Uint8Array(i),s=0;for(let o of n)r.set(o,s),s+=o.length;return new Blob([r],{type:"application/octet-stream"})}return new Blob([e],{type:"application/octet-stream"})}async computeFileFingerprint(e){let t=new z,n=new Uint8Array(await e.arrayBuffer()),i=1024,r=0;for(;r<n.length;){let s=Math.min(r+i,n.length),o=n.slice(r,s);t.push_chunk(o),r=s}return t.get_root()}async formFileMetadata(e,t,n,i,r){let s=P(e),o=P(t),c=new TextEncoder().encode(n);return await x(),new W(s,o,c,r,i)}async computeFileKey(e){return await x(),e.getFileKey()}};import{ensure0xPrefix as I}from"@storagehub-sdk/core";var A=class extends l{getHealth(e){return this.ctx.http.get("/health",{...e?{signal:e}:{}})}async getInfo(e){let t=await this.ctx.http.get("/info",{...e?{signal:e}:{}});return{client:t.client,version:t.version,mspId:I(t.mspId),multiaddresses:t.multiaddresses,ownerAccount:I(t.ownerAccount),paymentAccount:I(t.paymentAccount),status:t.status,activeSince:t.activeSince,uptime:t.uptime}}getStats(e){return this.ctx.http.get("/stats",{...e?{signal:e}:{}})}getValuePropositions(e){return this.ctx.http.get("/value-props",{...e?{signal:e}:{}})}async getPaymentStreams(e){let t=await this.withAuth();return this.ctx.http.get("/payment_streams",{...t?{headers:t}:{},...e?{signal:e}:{}})}};var B=class a extends l{config;context;auth;buckets;files;info;constructor(e,t,n){let i={config:e,http:t};super(i,n),this.config=e,this.context=i,this.auth=new b(this.context,n),this.buckets=new S(this.context,n),this.files=new F(this.context,n),this.info=new A(this.context,n)}static async connect(e,t){if(!e?.baseUrl)throw new Error("MspClient.connect: baseUrl is required");let n=new _({baseUrl:e.baseUrl,...e.timeoutMs!==void 0&&{timeoutMs:e.timeoutMs},...e.defaultHeaders!==void 0&&{defaultHeaders:e.defaultHeaders},...e.fetchImpl!==void 0&&{fetchImpl:e.fetchImpl}});if(!t)throw new Error("MspClient.connect: sessionProvider is required");return new a(e,n,t)}};export{B as MspClient};
|
|
2
2
|
//# sourceMappingURL=index.browser.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/MspClient.ts", "../src/modules/auth.ts", "../src/base.ts", "../src/modules/buckets.ts", "../src/modules/files.ts", "../src/modules/info.ts"],
|
|
4
|
-
"sourcesContent": ["import type { HttpClientConfig } from \"@storagehub-sdk/core\";\nimport { HttpClient } from \"@storagehub-sdk/core\";\nimport type { MspClientContext } from \"./context.js\";\nimport { AuthModule } from \"./modules/auth.js\";\nimport { BucketsModule } from \"./modules/buckets.js\";\nimport { ModuleBase } from \"./base.js\";\nimport { FilesModule } from \"./modules/files.js\";\nimport { InfoModule } from \"./modules/info.js\";\nimport type { SessionProvider } from \"./types.js\";\n\nexport class MspClient extends ModuleBase {\n public readonly config: HttpClientConfig;\n private readonly context: MspClientContext;\n public readonly auth: AuthModule;\n public readonly buckets: BucketsModule;\n public readonly files: FilesModule;\n public readonly info: InfoModule;\n\n private constructor(\n config: HttpClientConfig,\n http: HttpClient,\n sessionProvider: SessionProvider\n ) {\n const context: MspClientContext = { config, http };\n super(context, sessionProvider);\n this.config = config;\n this.context = context;\n this.auth = new AuthModule(this.context, sessionProvider);\n this.buckets = new BucketsModule(this.context, sessionProvider);\n this.files = new FilesModule(this.context, sessionProvider);\n this.info = new InfoModule(this.context, sessionProvider);\n }\n\n static async connect(\n config: HttpClientConfig,\n sessionProvider: SessionProvider\n ): Promise<MspClient> {\n if (!config?.baseUrl) throw new Error(\"MspClient.connect: baseUrl is required\");\n\n const http = new HttpClient({\n baseUrl: config.baseUrl,\n ...(config.timeoutMs !== undefined && { timeoutMs: config.timeoutMs }),\n ...(config.defaultHeaders !== undefined && {\n defaultHeaders: config.defaultHeaders\n }),\n ...(config.fetchImpl !== undefined && { fetchImpl: config.fetchImpl })\n });\n\n if (!sessionProvider) throw new Error(\"MspClient.connect: sessionProvider is required\");\n return new MspClient(config, http, sessionProvider);\n }\n}\n", "import type { NonceResponse, Session, UserInfo } from \"../types.js\";\nimport { getAddress, type WalletClient } from \"viem\";\nimport { ModuleBase } from \"../base.js\";\n\nconst DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS = 10;\nconst DEFAULT_SIWE_VERIFY_BACKOFF_MS = 100;\n\nexport class AuthModule extends ModuleBase {\n /**\n * Request nonce for SIWE.\n * - Input: EVM `address`, `chainId`.\n * - Output: message to sign.\n */\n private getNonce(address: string, chainId: number, signal?: AbortSignal): Promise<NonceResponse> {\n return this.ctx.http.post<NonceResponse>(\"/auth/nonce\", {\n body: { address, chainId },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n }\n\n /**\n * Verify SIWE signature.\n * - Persists `session` in context on success.\n */\n private async verify(message: string, signature: string, signal?: AbortSignal): Promise<Session> {\n const session = await this.ctx.http.post<Session>(\"/auth/verify\", {\n body: { message, signature },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n\n return session;\n }\n\n /**\n * Full SIWE flow using a `WalletClient`.\n * - Derives address, fetches nonce, signs message, verifies and stores session.\n */\n async SIWE(\n wallet: WalletClient,\n retry = DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS,\n signal?: AbortSignal\n ): Promise<Session> {\n // Resolve the current active account from the WalletClient.\n // - Browser wallets (e.g., MetaMask) surface the user-selected address here.\n // - Viem/local wallets must set `wallet.account` explicitly before calling.\n const account = wallet.account;\n const resolvedAddress = typeof account === \"string\" ? account : account?.address;\n if (!resolvedAddress || !account) {\n throw new Error(\n \"Wallet client has no active account; set wallet.account before calling SIWE\"\n );\n }\n // Get the checksummed address\n const address = getAddress(resolvedAddress);\n const chainId = await wallet.getChainId();\n const { message } = await this.getNonce(address, chainId, signal);\n\n // Sign using the active account resolved above (string or Account object)\n const signature = await wallet.signMessage({ account, message });\n\n // TODO: remove the retry logic once the backend is fixed.\n let lastError: unknown;\n for (let attemptIndex = 0; attemptIndex < retry; attemptIndex++) {\n try {\n return await this.verify(message, signature, signal);\n } catch (err) {\n lastError = err;\n await this.delay(DEFAULT_SIWE_VERIFY_BACKOFF_MS);\n }\n }\n throw lastError instanceof Error ? lastError : new Error(\"SIWE verification failed\");\n }\n\n private async delay(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Fetch authenticated user's profile.\n * - Requires valid `session` (Authorization header added automatically).\n */\n async getProfile(signal?: AbortSignal): Promise<UserInfo> {\n const headers = await this.withAuth();\n return this.ctx.http.get<UserInfo>(\"/auth/profile\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n}\n", "import type { MspClientContext } from \"./context.js\";\nimport type { SessionProvider } from \"./types.js\";\n\nexport abstract class ModuleBase {\n protected readonly ctx: MspClientContext;\n private readonly sessionProvider: SessionProvider;\n\n constructor(ctx: MspClientContext, sessionProvider: SessionProvider) {\n this.ctx = ctx;\n this.sessionProvider = sessionProvider;\n }\n\n protected async withAuth(\n headers?: Record<string, string>\n ): Promise<Record<string, string> | undefined> {\n const session = await this.sessionProvider();\n const token = session?.token;\n if (!token) return headers;\n return headers\n ? { ...headers, Authorization: `Bearer ${token}` }\n : { Authorization: `Bearer ${token}` };\n }\n\n /**\n * Normalize a user-provided path for HTTP query usage.\n * - Removes all leading '/' characters to avoid double slashes in URLs.\n * - Collapses any repeated slashes in the middle or at the end to a single '/'.\n * Examples:\n * \"/foo/bar\" -> \"foo/bar\"\n * \"///docs\" -> \"docs\"\n * \"foo//bar\" -> \"foo/bar\"\n * \"///a//b///\" -> \"a/b/\"\n * \"foo/bar\" -> \"foo/bar\" (unchanged)\n * \"/\" -> \"\"\n */\n protected normalizePath(path: string): string {\n // Drop leading slashes (offset === 0), collapse others to '/'\n return path.replace(/^\\/+|\\/{2,}/g, (_m, offset: number) => (offset === 0 ? \"\" : \"/\"));\n }\n}\n", "import type { Bucket, FileListResponse, GetFilesOptions } from \"../types.js\";\nimport { ModuleBase } from \"../base.js\";\n\nexport class BucketsModule extends ModuleBase {\n /** List all buckets for the current authenticated user */\n async listBuckets(signal?: AbortSignal): Promise<Bucket[]> {\n const headers = await this.withAuth();\n return this.ctx.http.get<Bucket[]>(\"/buckets\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get a specific bucket's metadata by its bucket ID */\n async getBucket(bucketId: string, signal?: AbortSignal): Promise<Bucket> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}`;\n return this.ctx.http.get<Bucket>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n\n /** List files/folders under a path for a bucket (root if no path) */\n async getFiles(bucketId: string, options?: GetFilesOptions): Promise<FileListResponse> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/files`;\n return this.ctx.http.get<FileListResponse>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n ...(options?.path ? { query: { path: this.normalizePath(options.path) } } : {})\n });\n }\n}\n", "import { ModuleBase } from \"../base.js\";\nimport type {\n DownloadOptions,\n DownloadResult,\n FileInfo,\n UploadOptions,\n UploadReceipt\n} from \"../types.js\";\nimport { FileMetadata, FileTrie, initWasm } from \"@storagehub-sdk/core\";\n\nexport class FilesModule extends ModuleBase {\n /** Get metadata for a file in a bucket by fileKey */\n async getFileInfo(bucketId: string, fileKey: string, signal?: AbortSignal): Promise<FileInfo> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/info/${encodeURIComponent(fileKey)}`;\n type FileInfoWire = Omit<FileInfo, \"uploadedAt\"> & { uploadedAt: string };\n const wire = await this.ctx.http.get<FileInfoWire>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n return {\n ...wire,\n uploadedAt: new Date(wire.uploadedAt)\n };\n }\n\n /** Upload a file to a bucket with a specific key */\n async uploadFile(\n bucketId: string,\n fileKey: string,\n file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown,\n owner: string,\n location: string,\n _options?: UploadOptions\n ): Promise<UploadReceipt> {\n void _options;\n\n await initWasm();\n\n const backendPath = `/buckets/${encodeURIComponent(bucketId)}/upload/${encodeURIComponent(fileKey)}`;\n const authHeaders = await this.withAuth();\n\n // Convert the file to a blob and get its size\n const fileBlob = await this.coerceToFormPart(file);\n const fileSize = fileBlob.size;\n\n // Compute the fingerprint first\n const fingerprint = await this.computeFileFingerprint(fileBlob);\n\n // Create the FileMetadata instance\n const metadata = await this.formFileMetadata(\n owner,\n bucketId,\n location,\n fingerprint,\n BigInt(fileSize)\n );\n\n // Compute the file key and ensure it matches the provided file key\n const computedFileKey = await this.computeFileKey(metadata);\n const expectedFileKeyBytes = this.hexToBytes(fileKey);\n if (\n computedFileKey.length !== expectedFileKeyBytes.length ||\n !computedFileKey.every((byte, index) => byte === expectedFileKeyBytes[index])\n ) {\n throw new Error(\n `Computed file key ${computedFileKey.toString()} does not match provided file key ${expectedFileKeyBytes.toString()}`\n );\n }\n\n // Encode the file metadata\n const encodedMetadata = metadata.encode();\n\n // Create the multipart form with both the file and its metadata\n const form = new FormData();\n const fileMetadataBlob = new Blob([new Uint8Array(encodedMetadata)], {\n type: \"application/octet-stream\"\n });\n form.append(\"file_metadata\", fileMetadataBlob, \"file_metadata\");\n form.append(\"file\", fileBlob, \"file\");\n\n const res = await this.ctx.http.put<UploadReceipt>(\n backendPath,\n authHeaders\n ? { body: form as unknown as BodyInit, headers: authHeaders }\n : { body: form as unknown as BodyInit }\n );\n return res;\n }\n\n /** Download a file by key */\n async downloadFile(fileKey: string, options?: DownloadOptions): Promise<DownloadResult> {\n const path = `/download/${encodeURIComponent(fileKey)}`;\n const baseHeaders: Record<string, string> = { Accept: \"*/*\" };\n if (options?.range) {\n const { start, end } = options.range;\n const rangeValue = `bytes=${start}-${end ?? \"\"}`;\n baseHeaders.Range = rangeValue;\n }\n\n const headers = await this.withAuth(baseHeaders);\n\n try {\n const res = await this.ctx.http.getRaw(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {})\n });\n\n if (!res.body) {\n throw new Error(\"Response body is null - unable to create stream\");\n }\n\n const contentType = res.headers.get(\"content-type\");\n const contentRange = res.headers.get(\"content-range\");\n const contentLengthHeader = res.headers.get(\"content-length\");\n const parsedLength = contentLengthHeader !== null ? Number(contentLengthHeader) : undefined;\n const contentLength =\n typeof parsedLength === \"number\" && Number.isFinite(parsedLength) ? parsedLength : null;\n\n return {\n stream: res.body,\n status: res.status,\n contentType,\n contentRange,\n contentLength\n };\n } catch (error) {\n // Handle HTTP errors by returning them as a DownloadResult with the error status\n if (\n error &&\n typeof error === \"object\" &&\n \"status\" in error &&\n typeof error.status === \"number\"\n ) {\n // Create an empty stream for error responses\n const emptyStream = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n }\n });\n\n return {\n stream: emptyStream,\n status: error.status,\n contentType: null,\n contentRange: null,\n contentLength: null\n };\n }\n // Re-throw non-HTTP errors\n throw error;\n }\n }\n\n // Helpers\n private async coerceToFormPart(\n file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown\n ): Promise<Blob> {\n if (typeof Blob !== \"undefined\" && file instanceof Blob) return file;\n if (file instanceof Uint8Array) return new Blob([file.buffer as ArrayBuffer]);\n if (typeof ArrayBuffer !== \"undefined\" && file instanceof ArrayBuffer) return new Blob([file]);\n\n // Handle ReadableStream by reading it into memory\n if (file instanceof ReadableStream) {\n const reader = file.getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n totalLength += value.length;\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n // Combine all chunks into a single Uint8Array\n const combined = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n return new Blob([combined], { type: \"application/octet-stream\" });\n }\n\n return new Blob([file as BlobPart], { type: \"application/octet-stream\" });\n }\n\n private async computeFileFingerprint(fileBlob: Blob): Promise<Uint8Array> {\n const trie = new FileTrie();\n const fileBytes = new Uint8Array(await fileBlob.arrayBuffer());\n\n // Process the file in 1KB chunks (matching CHUNK_SIZE from constants)\n const CHUNK_SIZE = 1024;\n let offset = 0;\n\n while (offset < fileBytes.length) {\n const end = Math.min(offset + CHUNK_SIZE, fileBytes.length);\n const chunk = fileBytes.slice(offset, end);\n trie.push_chunk(chunk);\n offset = end;\n }\n\n return trie.get_root();\n }\n\n private async formFileMetadata(\n owner: string,\n bucketId: string,\n location: string,\n fingerprint: Uint8Array,\n size: bigint\n ): Promise<FileMetadata> {\n const ownerBytes = this.hexToBytes(owner);\n const bucketIdBytes = this.hexToBytes(bucketId);\n const locationBytes = new TextEncoder().encode(location);\n await initWasm();\n return new FileMetadata(ownerBytes, bucketIdBytes, locationBytes, size, fingerprint);\n }\n\n private hexToBytes(hex: string): Uint8Array {\n if (!hex) {\n throw new Error(\"hex string cannot be empty\");\n }\n\n const cleanHex = hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n\n if (cleanHex.length % 2 !== 0) {\n throw new Error(\"hex string must have an even number of characters\");\n }\n\n if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {\n throw new Error(\"hex string contains invalid characters\");\n }\n\n return new Uint8Array(cleanHex.match(/.{2}/g)?.map((byte) => Number.parseInt(byte, 16)) || []);\n }\n\n private async computeFileKey(fileMetadata: FileMetadata): Promise<Uint8Array> {\n await initWasm();\n return fileMetadata.getFileKey();\n }\n}\n", "import { ModuleBase } from \"../base.js\";\nimport type {\n HealthStatus,\n InfoResponse,\n PaymentStreamsResponse,\n StatsResponse,\n ValueProp\n} from \"../types.js\";\n\nexport class InfoModule extends ModuleBase {\n getHealth(signal?: AbortSignal): Promise<HealthStatus> {\n return this.ctx.http.get<HealthStatus>(\"/health\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get general MSP information */\n getInfo(signal?: AbortSignal): Promise<InfoResponse> {\n return this.ctx.http.get<InfoResponse>(\"/info\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get MSP statistics */\n getStats(signal?: AbortSignal): Promise<StatsResponse> {\n return this.ctx.http.get<StatsResponse>(\"/stats\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get available value propositions */\n getValuePropositions(signal?: AbortSignal): Promise<ValueProp[]> {\n return this.ctx.http.get<ValueProp[]>(\"/value-props\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get payment streams for current authenticated user */\n async getPaymentStreams(signal?: AbortSignal): Promise<PaymentStreamsResponse> {\n const headers = await this.withAuth();\n return this.ctx.http.get<PaymentStreamsResponse>(\"/payment_streams\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n}\n"],
|
|
5
|
-
"mappings": "AACA,OAAS,cAAAA,MAAkB,uBCA3B,OAAS,cAAAC,MAAqC,OCEvC,IAAeC,EAAf,KAA0B,CACZ,IACF,gBAEjB,YAAYC,EAAuBC,EAAkC,CACnE,KAAK,IAAMD,EACX,KAAK,gBAAkBC,CACzB,CAEA,MAAgB,SACdC,EAC6C,CAE7C,IAAMC,GADU,MAAM,KAAK,gBAAgB,IACpB,MACvB,OAAKA,EACED,EACH,CAAE,GAAGA,EAAS,cAAe,UAAUC,CAAK,EAAG,EAC/C,CAAE,cAAe,UAAUA,CAAK,EAAG,EAHpBD,CAIrB,CAcU,cAAcE,EAAsB,CAE5C,OAAOA,EAAK,QAAQ,eAAgB,CAACC,EAAIC,IAAoBA,IAAW,EAAI,GAAK,GAAI,CACvF,CACF,EDnCA,IAAMC,EAAoC,GACpCC,EAAiC,IAE1BC,EAAN,cAAyBC,CAAW,
|
|
6
|
-
"names": ["HttpClient", "getAddress", "ModuleBase", "ctx", "sessionProvider", "headers", "token", "path", "_m", "offset", "DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS", "DEFAULT_SIWE_VERIFY_BACKOFF_MS", "AuthModule", "ModuleBase", "address", "chainId", "signal", "message", "signature", "wallet", "retry", "account", "resolvedAddress", "getAddress", "lastError", "attemptIndex", "err", "ms", "resolve", "headers", "BucketsModule", "ModuleBase", "signal", "headers", "bucketId", "path", "options", "FileMetadata", "FileTrie", "initWasm", "FilesModule", "ModuleBase", "bucketId", "fileKey", "signal", "headers", "path", "wire", "file", "owner", "location", "_options", "backendPath", "authHeaders", "fileBlob", "fileSize", "fingerprint", "metadata", "computedFileKey", "expectedFileKeyBytes", "byte", "index", "encodedMetadata", "form", "fileMetadataBlob", "options", "baseHeaders", "start", "end", "rangeValue", "res", "contentType", "contentRange", "contentLengthHeader", "parsedLength", "contentLength", "error", "controller", "reader", "chunks", "totalLength", "done", "value", "combined", "offset", "chunk", "trie", "fileBytes", "CHUNK_SIZE", "size", "ownerBytes", "bucketIdBytes", "locationBytes", "
|
|
4
|
+
"sourcesContent": ["import type { HttpClientConfig } from \"@storagehub-sdk/core\";\nimport { HttpClient } from \"@storagehub-sdk/core\";\nimport type { MspClientContext } from \"./context.js\";\nimport { AuthModule } from \"./modules/auth.js\";\nimport { BucketsModule } from \"./modules/buckets.js\";\nimport { ModuleBase } from \"./base.js\";\nimport { FilesModule } from \"./modules/files.js\";\nimport { InfoModule } from \"./modules/info.js\";\nimport type { SessionProvider } from \"./types.js\";\n\nexport class MspClient extends ModuleBase {\n public readonly config: HttpClientConfig;\n private readonly context: MspClientContext;\n public readonly auth: AuthModule;\n public readonly buckets: BucketsModule;\n public readonly files: FilesModule;\n public readonly info: InfoModule;\n\n private constructor(\n config: HttpClientConfig,\n http: HttpClient,\n sessionProvider: SessionProvider\n ) {\n const context: MspClientContext = { config, http };\n super(context, sessionProvider);\n this.config = config;\n this.context = context;\n this.auth = new AuthModule(this.context, sessionProvider);\n this.buckets = new BucketsModule(this.context, sessionProvider);\n this.files = new FilesModule(this.context, sessionProvider);\n this.info = new InfoModule(this.context, sessionProvider);\n }\n\n static async connect(\n config: HttpClientConfig,\n sessionProvider: SessionProvider\n ): Promise<MspClient> {\n if (!config?.baseUrl) throw new Error(\"MspClient.connect: baseUrl is required\");\n\n const http = new HttpClient({\n baseUrl: config.baseUrl,\n ...(config.timeoutMs !== undefined && { timeoutMs: config.timeoutMs }),\n ...(config.defaultHeaders !== undefined && {\n defaultHeaders: config.defaultHeaders\n }),\n ...(config.fetchImpl !== undefined && { fetchImpl: config.fetchImpl })\n });\n\n if (!sessionProvider) throw new Error(\"MspClient.connect: sessionProvider is required\");\n return new MspClient(config, http, sessionProvider);\n }\n}\n", "import type { NonceResponse, Session, UserInfo } from \"../types.js\";\nimport { getAddress, type WalletClient } from \"viem\";\nimport { ModuleBase } from \"../base.js\";\n\nconst DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS = 10;\nconst DEFAULT_SIWE_VERIFY_BACKOFF_MS = 100;\n\nexport class AuthModule extends ModuleBase {\n /**\n * Request a nonce (challenge message) for Sign-In with Ethereum (SIWE).\n *\n * **Advanced use only:** Most users should use the `SIWE()` method instead, which handles the complete authentication flow automatically. This method is exposed only for custom authentication flows.\n *\n * **Important:** The challenge message expires after a short time (typically 5 minutes). You must call `verify()` with a valid signature before expiration.\n *\n * @param address - The Ethereum address requesting authentication (checksummed format recommended).\n * @param chainId - The chain ID the user is connected to.\n * @param domain - The domain (host[:port]) for the SIWE message (e.g., \"datahaven.app\" or \"localhost:3000\").\n * @param uri - The full URI of your application (e.g., \"https://datahaven.app\" or \"http://localhost:3000\").\n * @param signal - Optional AbortSignal for request cancellation.\n * @returns A promise resolving to the SIWE challenge message to be signed.\n */\n public getNonce(\n address: string,\n chainId: number,\n domain: string,\n uri: string,\n signal?: AbortSignal\n ): Promise<NonceResponse> {\n return this.ctx.http.post<NonceResponse>(\"/auth/nonce\", {\n body: {\n address,\n chainId,\n domain,\n uri\n },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n }\n\n /**\n * Verify a Sign-In with Ethereum (SIWE) signature.\n *\n * **Advanced use only:** Most users should use the `SIWE()` method instead, which handles the complete authentication flow automatically. This method is exposed only for custom authentication flows.\n *\n * **Important:** You must store the returned Session object and provide it via the `sessionProvider` function passed to `MspClient.connect()`.\n * The session is not automatically persisted - you are responsible for managing session storage and ensuring your `sessionProvider` returns it for subsequent authenticated requests.\n *\n * @param message - The SIWE challenge message received from `getNonce()`.\n * @param signature - The signature of the message signed by the user's wallet.\n * @param signal - Optional AbortSignal for request cancellation.\n * @returns A promise resolving to a Session object that you must store and provide via your sessionProvider.\n */\n public async verify(message: string, signature: string, signal?: AbortSignal): Promise<Session> {\n const session = await this.ctx.http.post<Session>(\"/auth/verify\", {\n body: { message, signature },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n\n return session;\n }\n\n /**\n * Complete Sign-In with Ethereum (SIWE) authentication flow using a `WalletClient`.\n *\n * This is the recommended method for authentication. It handles the complete flow automatically:\n * derives the wallet address, fetches a nonce, prompts the user to sign the message, verifies the signature,\n * and returns a session token.\n *\n * **Important:** You must store the returned Session object and provide it via the `sessionProvider` function\n * passed to `MspClient.connect()`. The session is not automatically persisted - you are responsible for managing\n * session storage and ensuring your `sessionProvider` returns it for subsequent authenticated requests.\n *\n * **Note:** This method includes automatic retry logic for verification requests (default: 10 attempts with 100ms backoff).\n * The retry behavior can be customized via the `retry` parameter.\n *\n * @param wallet - The Viem `WalletClient` instance. Must have an active account set (`wallet.account`).\n * - Browser wallets (e.g., MetaMask) automatically surface the user-selected address.\n * - Viem/local wallets must set `wallet.account` explicitly before calling.\n * @param domain - The domain (host[:port]) for the SIWE message (e.g., \"datahaven.app\" or \"localhost:3000\").\n * @param uri - The full URI of your application (e.g., \"https://datahaven.app\" or \"http://localhost:3000\").\n * @param retry - Number of retry attempts for verification requests (default: 10).\n * @param signal - Optional AbortSignal for request cancellation.\n * @returns A promise resolving to a Session object that you must store and provide via your sessionProvider.\n */\n async SIWE(\n wallet: WalletClient,\n domain: string,\n uri: string,\n retry = DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS,\n signal?: AbortSignal\n ): Promise<Session> {\n // Resolve the current active account from the WalletClient.\n // - Browser wallets (e.g., MetaMask) surface the user-selected address here.\n // - Viem/local wallets must set `wallet.account` explicitly before calling.\n const account = wallet.account;\n const resolvedAddress = typeof account === \"string\" ? account : account?.address;\n if (!resolvedAddress || !account) {\n throw new Error(\n \"Wallet client has no active account; set wallet.account before calling SIWE\"\n );\n }\n // Get the checksummed address\n const address = getAddress(resolvedAddress);\n const chainId = await wallet.getChainId();\n const { message } = await this.getNonce(address, chainId, domain, uri, signal);\n\n // Sign using the active account resolved above (string or Account object)\n const signature = await wallet.signMessage({ account, message });\n\n // TODO: remove the retry logic once the backend is fixed.\n let lastError: unknown;\n for (let attemptIndex = 0; attemptIndex < retry; attemptIndex++) {\n try {\n return await this.verify(message, signature, signal);\n } catch (err) {\n lastError = err;\n await this.delay(DEFAULT_SIWE_VERIFY_BACKOFF_MS);\n }\n }\n throw lastError instanceof Error ? lastError : new Error(\"SIWE verification failed\");\n }\n\n private async delay(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Fetch authenticated user's profile.\n * - Requires valid `session` (Authorization header added automatically).\n */\n async getProfile(signal?: AbortSignal): Promise<UserInfo> {\n const headers = await this.withAuth();\n return this.ctx.http.get<UserInfo>(\"/auth/profile\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n}\n", "import type { MspClientContext } from \"./context.js\";\nimport type { SessionProvider } from \"./types.js\";\n\nexport abstract class ModuleBase {\n protected readonly ctx: MspClientContext;\n private readonly sessionProvider: SessionProvider;\n\n constructor(ctx: MspClientContext, sessionProvider: SessionProvider) {\n this.ctx = ctx;\n this.sessionProvider = sessionProvider;\n }\n\n protected async withAuth(\n headers?: Record<string, string>\n ): Promise<Record<string, string> | undefined> {\n const session = await this.sessionProvider();\n const token = session?.token;\n if (!token) return headers;\n return headers\n ? { ...headers, Authorization: `Bearer ${token}` }\n : { Authorization: `Bearer ${token}` };\n }\n\n /**\n * Normalize a user-provided path for HTTP query usage.\n * - Removes all leading '/' characters to avoid double slashes in URLs.\n * - Collapses any repeated slashes in the middle or at the end to a single '/'.\n * Examples:\n * \"/foo/bar\" -> \"foo/bar\"\n * \"///docs\" -> \"docs\"\n * \"foo//bar\" -> \"foo/bar\"\n * \"///a//b///\" -> \"a/b/\"\n * \"foo/bar\" -> \"foo/bar\" (unchanged)\n * \"/\" -> \"\"\n */\n protected normalizePath(path: string): string {\n // Drop leading slashes (offset === 0), collapse others to '/'\n return path.replace(/^\\/+|\\/{2,}/g, (_m, offset: number) => (offset === 0 ? \"\" : \"/\"));\n }\n}\n", "import type { Bucket, FileListResponse, GetFilesOptions, FileTree, FileStatus } from \"../types.js\";\nimport { ModuleBase } from \"../base.js\";\nimport { ensure0xPrefix, parseDate } from \"@storagehub-sdk/core\";\n\n// Wire types received from backend JSON responses\ntype FileTreeWireFile = {\n name: string;\n type: \"file\";\n sizeBytes: number;\n fileKey: string; // may lack 0x\n status: FileStatus;\n uploadedAt: string; // ISO timestamp\n};\n\ntype FileTreeWireFolder = {\n name: string;\n type: \"folder\";\n children?: readonly FileTreeWire[];\n};\n\ntype FileTreeWire = FileTreeWireFile | FileTreeWireFolder;\n\ntype FileListResponseWire =\n | { bucketId: string; files: readonly FileTreeWire[] }\n | { bucketId: string; tree: FileTreeWireFolder };\n\n/** Recursively fix hex prefixes in FileTree structures */\nfunction fixFileTree(item: FileTreeWire): FileTree {\n if (item.type === \"file\") {\n return {\n name: item.name,\n type: item.type,\n sizeBytes: item.sizeBytes,\n fileKey: ensure0xPrefix(item.fileKey),\n status: item.status,\n uploadedAt: parseDate(item.uploadedAt)\n };\n }\n return {\n name: item.name,\n type: item.type,\n children: (item.children ?? []).map(fixFileTree)\n };\n}\n\nexport class BucketsModule extends ModuleBase {\n /** List all buckets for the current authenticated user */\n async listBuckets(signal?: AbortSignal): Promise<Bucket[]> {\n const headers = await this.withAuth();\n const wire = await this.ctx.http.get<Bucket[]>(\"/buckets\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n\n return wire.map((bucket: Bucket) => ({\n bucketId: ensure0xPrefix(bucket.bucketId),\n name: bucket.name,\n root: ensure0xPrefix(bucket.root),\n isPublic: bucket.isPublic,\n sizeBytes: bucket.sizeBytes,\n valuePropId: ensure0xPrefix(bucket.valuePropId),\n fileCount: bucket.fileCount\n }));\n }\n\n /** Get a specific bucket's metadata by its bucket ID */\n async getBucket(bucketId: string, signal?: AbortSignal): Promise<Bucket> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}`;\n\n const wire = await this.ctx.http.get<Bucket>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n\n return {\n bucketId: ensure0xPrefix(wire.bucketId),\n name: wire.name,\n root: ensure0xPrefix(wire.root),\n isPublic: wire.isPublic,\n sizeBytes: wire.sizeBytes,\n valuePropId: ensure0xPrefix(wire.valuePropId),\n fileCount: wire.fileCount\n };\n }\n\n /** List files/folders under a path for a bucket (root if no path) */\n async getFiles(bucketId: string, options?: GetFilesOptions): Promise<FileListResponse> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/files`;\n\n const wire = await this.ctx.http.get<FileListResponseWire>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n ...(options?.path ? { query: { path: this.normalizePath(options.path) } } : {})\n });\n\n const filesWire: readonly FileTreeWire[] = \"files\" in wire ? wire.files : [wire.tree];\n const files: FileTree[] = filesWire.map(fixFileTree);\n const tree = files[0];\n return {\n bucketId: ensure0xPrefix(wire.bucketId),\n files,\n ...(tree ? { tree } : {})\n } as unknown as FileListResponse;\n }\n}\n", "import {\n ensure0xPrefix,\n FileMetadata,\n FileTrie,\n hexToBytes,\n initWasm,\n parseDate\n} from \"@storagehub-sdk/core\";\nimport { ModuleBase } from \"../base.js\";\nimport type {\n DownloadOptions,\n DownloadResult,\n FileStatus,\n StorageFileInfo,\n UploadOptions,\n UploadReceipt\n} from \"../types.js\";\n\nexport class FilesModule extends ModuleBase {\n /** Get metadata for a file in a bucket by fileKey */\n async getFileInfo(\n bucketId: string,\n fileKey: string,\n signal?: AbortSignal\n ): Promise<StorageFileInfo> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/info/${encodeURIComponent(fileKey)}`;\n\n const wire = await this.ctx.http.get<{\n fileKey: string;\n fingerprint: string;\n bucketId: string;\n location: string;\n size: string; // Backend sends as string to avoid precision loss\n isPublic: boolean;\n uploadedAt: string; // ISO string, not Date object\n status: string;\n blockHash: string; // Block hash where file was created\n txHash?: string; // Optional EVM transaction hash\n }>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n\n return {\n fileKey: ensure0xPrefix(wire.fileKey),\n fingerprint: ensure0xPrefix(wire.fingerprint),\n bucketId: ensure0xPrefix(wire.bucketId),\n location: wire.location,\n size: BigInt(wire.size),\n isPublic: wire.isPublic,\n uploadedAt: parseDate(wire.uploadedAt),\n status: wire.status as FileStatus,\n blockHash: ensure0xPrefix(wire.blockHash),\n ...(wire.txHash ? { txHash: ensure0xPrefix(wire.txHash) } : {})\n };\n }\n\n /** Upload a file to a bucket with a specific key */\n async uploadFile(\n bucketId: string,\n fileKey: string,\n file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown,\n owner: string,\n location: string,\n _options?: UploadOptions\n ): Promise<UploadReceipt> {\n void _options;\n\n await initWasm();\n\n const backendPath = `/buckets/${encodeURIComponent(bucketId)}/upload/${encodeURIComponent(fileKey)}`;\n const authHeaders = await this.withAuth();\n\n // Convert the file to a blob and get its size\n const fileBlob = await this.coerceToFormPart(file);\n const fileSize = fileBlob.size;\n\n // Compute the fingerprint first\n const fingerprint = await this.computeFileFingerprint(fileBlob);\n\n // Create the FileMetadata instance\n const metadata = await this.formFileMetadata(\n owner,\n bucketId,\n location,\n fingerprint,\n BigInt(fileSize)\n );\n\n // Compute the file key and ensure it matches the provided file key\n const computedFileKey = await this.computeFileKey(metadata);\n const expectedFileKeyBytes = hexToBytes(fileKey);\n if (\n computedFileKey.length !== expectedFileKeyBytes.length ||\n !computedFileKey.every((byte, index) => byte === expectedFileKeyBytes[index])\n ) {\n throw new Error(\n `Computed file key ${computedFileKey.toString()} does not match provided file key ${expectedFileKeyBytes.toString()}`\n );\n }\n\n // Encode the file metadata\n const encodedMetadata = metadata.encode();\n\n // Create the multipart form with both the file and its metadata\n const form = new FormData();\n const fileMetadataBlob = new Blob([new Uint8Array(encodedMetadata)], {\n type: \"application/octet-stream\"\n });\n form.append(\"file_metadata\", fileMetadataBlob, \"file_metadata\");\n form.append(\"file\", fileBlob, \"file\");\n\n const res = await this.ctx.http.put<UploadReceipt>(\n backendPath,\n authHeaders\n ? { body: form as unknown as BodyInit, headers: authHeaders }\n : { body: form as unknown as BodyInit }\n );\n return res;\n }\n\n /** Download a file by key */\n async downloadFile(fileKey: string, options?: DownloadOptions): Promise<DownloadResult> {\n const path = `/download/${encodeURIComponent(fileKey)}`;\n const baseHeaders: Record<string, string> = { Accept: \"*/*\" };\n if (options?.range) {\n const { start, end } = options.range;\n const rangeValue = `bytes=${start}-${end ?? \"\"}`;\n baseHeaders.Range = rangeValue;\n }\n\n const headers = await this.withAuth(baseHeaders);\n\n try {\n const res = await this.ctx.http.getRaw(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {})\n });\n\n if (!res.body) {\n throw new Error(\"Response body is null - unable to create stream\");\n }\n\n const contentType = res.headers.get(\"content-type\");\n const contentRange = res.headers.get(\"content-range\");\n const contentLengthHeader = res.headers.get(\"content-length\");\n const parsedLength = contentLengthHeader !== null ? Number(contentLengthHeader) : undefined;\n const contentLength =\n typeof parsedLength === \"number\" && Number.isFinite(parsedLength) ? parsedLength : null;\n\n return {\n stream: res.body,\n status: res.status,\n contentType,\n contentRange,\n contentLength\n };\n } catch (error) {\n // Handle HTTP errors by returning them as a DownloadResult with the error status\n if (this.isHttpError(error)) {\n return {\n stream: this.createEmptyStream(),\n status: error.status,\n contentType: null,\n contentRange: null,\n contentLength: null\n };\n }\n // Re-throw non-HTTP errors\n throw error;\n }\n }\n\n // Helpers\n private isHttpError(error: unknown): error is { status: number } {\n return (\n error !== null &&\n typeof error === \"object\" &&\n \"status\" in error &&\n typeof error.status === \"number\"\n );\n }\n\n private createEmptyStream(): ReadableStream<Uint8Array> {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n }\n });\n }\n\n private async coerceToFormPart(\n file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown\n ): Promise<Blob> {\n if (typeof Blob !== \"undefined\" && file instanceof Blob) return file;\n if (file instanceof Uint8Array) return new Blob([file.buffer as ArrayBuffer]);\n if (typeof ArrayBuffer !== \"undefined\" && file instanceof ArrayBuffer) return new Blob([file]);\n\n // Handle ReadableStream by reading it into memory\n if (file instanceof ReadableStream) {\n const reader = file.getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n totalLength += value.length;\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n // Combine all chunks into a single Uint8Array\n const combined = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n return new Blob([combined], { type: \"application/octet-stream\" });\n }\n\n return new Blob([file as BlobPart], { type: \"application/octet-stream\" });\n }\n\n private async computeFileFingerprint(fileBlob: Blob): Promise<Uint8Array> {\n const trie = new FileTrie();\n const fileBytes = new Uint8Array(await fileBlob.arrayBuffer());\n\n // Process the file in 1KB chunks (matching CHUNK_SIZE from constants)\n const CHUNK_SIZE = 1024;\n let offset = 0;\n\n while (offset < fileBytes.length) {\n const end = Math.min(offset + CHUNK_SIZE, fileBytes.length);\n const chunk = fileBytes.slice(offset, end);\n trie.push_chunk(chunk);\n offset = end;\n }\n\n return trie.get_root();\n }\n\n private async formFileMetadata(\n owner: string,\n bucketId: string,\n location: string,\n fingerprint: Uint8Array,\n size: bigint\n ): Promise<FileMetadata> {\n const ownerBytes = hexToBytes(owner);\n const bucketIdBytes = hexToBytes(bucketId);\n const locationBytes = new TextEncoder().encode(location);\n await initWasm();\n return new FileMetadata(ownerBytes, bucketIdBytes, locationBytes, size, fingerprint);\n }\n\n private async computeFileKey(fileMetadata: FileMetadata): Promise<Uint8Array> {\n await initWasm();\n return fileMetadata.getFileKey();\n }\n}\n", "import { ModuleBase } from \"../base.js\";\nimport type {\n HealthStatus,\n InfoResponse,\n PaymentStreamsResponse,\n StatsResponse,\n ValueProp\n} from \"../types.js\";\nimport { ensure0xPrefix } from \"@storagehub-sdk/core\";\n\nexport class InfoModule extends ModuleBase {\n getHealth(signal?: AbortSignal): Promise<HealthStatus> {\n return this.ctx.http.get<HealthStatus>(\"/health\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get general MSP information */\n async getInfo(signal?: AbortSignal): Promise<InfoResponse> {\n const wire = await this.ctx.http.get<{\n client: string;\n version: string;\n mspId: string;\n multiaddresses: string[];\n ownerAccount: string;\n paymentAccount: string;\n status: string;\n activeSince: number;\n uptime: string;\n }>(\"/info\", {\n ...(signal ? { signal } : {})\n });\n\n return {\n client: wire.client,\n version: wire.version,\n mspId: ensure0xPrefix(wire.mspId),\n multiaddresses: wire.multiaddresses,\n ownerAccount: ensure0xPrefix(wire.ownerAccount), // Ensure 0x prefix (backend has it, but TypeScript needs guarantee)\n paymentAccount: ensure0xPrefix(wire.paymentAccount), // Ensure 0x prefix (backend has it, but TypeScript needs guarantee)\n status: wire.status,\n activeSince: wire.activeSince,\n uptime: wire.uptime\n };\n }\n\n /** Get MSP statistics */\n getStats(signal?: AbortSignal): Promise<StatsResponse> {\n return this.ctx.http.get<StatsResponse>(\"/stats\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get available value propositions */\n getValuePropositions(signal?: AbortSignal): Promise<ValueProp[]> {\n return this.ctx.http.get<ValueProp[]>(\"/value-props\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get payment streams for current authenticated user */\n async getPaymentStreams(signal?: AbortSignal): Promise<PaymentStreamsResponse> {\n const headers = await this.withAuth();\n return this.ctx.http.get<PaymentStreamsResponse>(\"/payment_streams\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["HttpClient", "getAddress", "ModuleBase", "ctx", "sessionProvider", "headers", "token", "path", "_m", "offset", "DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS", "DEFAULT_SIWE_VERIFY_BACKOFF_MS", "AuthModule", "ModuleBase", "address", "chainId", "domain", "uri", "signal", "message", "signature", "wallet", "retry", "account", "resolvedAddress", "getAddress", "lastError", "attemptIndex", "err", "ms", "resolve", "headers", "ensure0xPrefix", "parseDate", "fixFileTree", "item", "BucketsModule", "ModuleBase", "signal", "headers", "bucket", "bucketId", "path", "wire", "options", "files", "tree", "ensure0xPrefix", "FileMetadata", "FileTrie", "hexToBytes", "initWasm", "parseDate", "FilesModule", "ModuleBase", "bucketId", "fileKey", "signal", "headers", "path", "wire", "ensure0xPrefix", "parseDate", "file", "owner", "location", "_options", "initWasm", "backendPath", "authHeaders", "fileBlob", "fileSize", "fingerprint", "metadata", "computedFileKey", "expectedFileKeyBytes", "hexToBytes", "byte", "index", "encodedMetadata", "form", "fileMetadataBlob", "options", "baseHeaders", "start", "end", "rangeValue", "res", "contentType", "contentRange", "contentLengthHeader", "parsedLength", "contentLength", "error", "controller", "reader", "chunks", "totalLength", "done", "value", "combined", "offset", "chunk", "trie", "FileTrie", "fileBytes", "CHUNK_SIZE", "size", "ownerBytes", "bucketIdBytes", "locationBytes", "FileMetadata", "fileMetadata", "ensure0xPrefix", "InfoModule", "ModuleBase", "signal", "wire", "headers", "MspClient", "_MspClient", "ModuleBase", "config", "http", "sessionProvider", "context", "AuthModule", "BucketsModule", "FilesModule", "InfoModule", "HttpClient"]
|
|
7
7
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { MspClient } from "./MspClient.js";
|
|
2
|
-
export type { Bucket, Capacity, DownloadOptions, DownloadResult, FileTree,
|
|
2
|
+
export type { Bucket, Capacity, DownloadOptions, DownloadResult, FileTree, StorageFileInfo, FileListResponse, HealthStatus, InfoResponse, NonceResponse, AuthState, AuthStatus, UserInfo, PaymentStreamInfo, PaymentStreamsResponse, StatsResponse, UploadOptions, UploadReceipt, ValueProp, Session, SessionProvider } from "./types.js";
|
package/dist/index.node.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{HttpClient as
|
|
1
|
+
import{HttpClient as _}from"@storagehub-sdk/core";import{getAddress as M}from"viem";var l=class{ctx;sessionProvider;constructor(e,t){this.ctx=e,this.sessionProvider=t}async withAuth(e){let n=(await this.sessionProvider())?.token;return n?e?{...e,Authorization:`Bearer ${n}`}:{Authorization:`Bearer ${n}`}:e}normalizePath(e){return e.replace(/^\/+|\/{2,}/g,(t,n)=>n===0?"":"/")}};var T=10,H=100,b=class extends l{getNonce(e,t,n,i,r){return this.ctx.http.post("/auth/nonce",{body:{address:e,chainId:t,domain:n,uri:i},headers:{"Content-Type":"application/json"},...r?{signal:r}:{}})}async verify(e,t,n){return await this.ctx.http.post("/auth/verify",{body:{message:e,signature:t},headers:{"Content-Type":"application/json"},...n?{signal:n}:{}})}async SIWE(e,t,n,i=T,r){let s=e.account,o=typeof s=="string"?s:s?.address;if(!o||!s)throw new Error("Wallet client has no active account; set wallet.account before calling SIWE");let c=M(o),p=await e.getChainId(),{message:u}=await this.getNonce(c,p,t,n,r),y=await e.signMessage({account:s,message:u}),h;for(let m=0;m<i;m++)try{return await this.verify(u,y,r)}catch(f){h=f,await this.delay(H)}throw h instanceof Error?h:new Error("SIWE verification failed")}async delay(e){await new Promise(t=>setTimeout(t,e))}async getProfile(e){let t=await this.withAuth();return this.ctx.http.get("/auth/profile",{...t?{headers:t}:{},...e?{signal:e}:{}})}};import{ensure0xPrefix as d,parseDate as E}from"@storagehub-sdk/core";function R(a){return a.type==="file"?{name:a.name,type:a.type,sizeBytes:a.sizeBytes,fileKey:d(a.fileKey),status:a.status,uploadedAt:E(a.uploadedAt)}:{name:a.name,type:a.type,children:(a.children??[]).map(R)}}var S=class extends l{async listBuckets(e){let t=await this.withAuth();return(await this.ctx.http.get("/buckets",{...t?{headers:t}:{},...e?{signal:e}:{}})).map(i=>({bucketId:d(i.bucketId),name:i.name,root:d(i.root),isPublic:i.isPublic,sizeBytes:i.sizeBytes,valuePropId:d(i.valuePropId),fileCount:i.fileCount}))}async getBucket(e,t){let n=await this.withAuth(),i=`/buckets/${encodeURIComponent(e)}`,r=await this.ctx.http.get(i,{...n?{headers:n}:{},...t?{signal:t}:{}});return{bucketId:d(r.bucketId),name:r.name,root:d(r.root),isPublic:r.isPublic,sizeBytes:r.sizeBytes,valuePropId:d(r.valuePropId),fileCount:r.fileCount}}async getFiles(e,t){let n=await this.withAuth(),i=`/buckets/${encodeURIComponent(e)}/files`,r=await this.ctx.http.get(i,{...n?{headers:n}:{},...t?.signal?{signal:t.signal}:{},...t?.path?{query:{path:this.normalizePath(t.path)}}:{}}),o=("files"in r?r.files:[r.tree]).map(R),c=o[0];return{bucketId:d(r.bucketId),files:o,...c?{tree:c}:{}}}};import{ensure0xPrefix as g,FileMetadata as W,FileTrie as z,hexToBytes as P,initWasm as x,parseDate as L}from"@storagehub-sdk/core";var F=class extends l{async getFileInfo(e,t,n){let i=await this.withAuth(),r=`/buckets/${encodeURIComponent(e)}/info/${encodeURIComponent(t)}`,s=await this.ctx.http.get(r,{...i?{headers:i}:{},...n?{signal:n}:{}});return{fileKey:g(s.fileKey),fingerprint:g(s.fingerprint),bucketId:g(s.bucketId),location:s.location,size:BigInt(s.size),isPublic:s.isPublic,uploadedAt:L(s.uploadedAt),status:s.status,blockHash:g(s.blockHash),...s.txHash?{txHash:g(s.txHash)}:{}}}async uploadFile(e,t,n,i,r,s){await x();let o=`/buckets/${encodeURIComponent(e)}/upload/${encodeURIComponent(t)}`,c=await this.withAuth(),p=await this.coerceToFormPart(n),u=p.size,y=await this.computeFileFingerprint(p),h=await this.formFileMetadata(i,e,r,y,BigInt(u)),m=await this.computeFileKey(h),f=P(t);if(m.length!==f.length||!m.every((U,v)=>U===f[v]))throw new Error(`Computed file key ${m.toString()} does not match provided file key ${f.toString()}`);let C=h.encode(),w=new FormData,k=new Blob([new Uint8Array(C)],{type:"application/octet-stream"});return w.append("file_metadata",k,"file_metadata"),w.append("file",p,"file"),await this.ctx.http.put(o,c?{body:w,headers:c}:{body:w})}async downloadFile(e,t){let n=`/download/${encodeURIComponent(e)}`,i={Accept:"*/*"};if(t?.range){let{start:s,end:o}=t.range,c=`bytes=${s}-${o??""}`;i.Range=c}let r=await this.withAuth(i);try{let s=await this.ctx.http.getRaw(n,{...r?{headers:r}:{},...t?.signal?{signal:t.signal}:{}});if(!s.body)throw new Error("Response body is null - unable to create stream");let o=s.headers.get("content-type"),c=s.headers.get("content-range"),p=s.headers.get("content-length"),u=p!==null?Number(p):void 0,y=typeof u=="number"&&Number.isFinite(u)?u:null;return{stream:s.body,status:s.status,contentType:o,contentRange:c,contentLength:y}}catch(s){if(this.isHttpError(s))return{stream:this.createEmptyStream(),status:s.status,contentType:null,contentRange:null,contentLength:null};throw s}}isHttpError(e){return e!==null&&typeof e=="object"&&"status"in e&&typeof e.status=="number"}createEmptyStream(){return new ReadableStream({start(e){e.close()}})}async coerceToFormPart(e){if(typeof Blob<"u"&&e instanceof Blob)return e;if(e instanceof Uint8Array)return new Blob([e.buffer]);if(typeof ArrayBuffer<"u"&&e instanceof ArrayBuffer)return new Blob([e]);if(e instanceof ReadableStream){let t=e.getReader(),n=[],i=0;try{for(;;){let{done:o,value:c}=await t.read();if(o)break;c&&(n.push(c),i+=c.length)}}finally{t.releaseLock()}let r=new Uint8Array(i),s=0;for(let o of n)r.set(o,s),s+=o.length;return new Blob([r],{type:"application/octet-stream"})}return new Blob([e],{type:"application/octet-stream"})}async computeFileFingerprint(e){let t=new z,n=new Uint8Array(await e.arrayBuffer()),i=1024,r=0;for(;r<n.length;){let s=Math.min(r+i,n.length),o=n.slice(r,s);t.push_chunk(o),r=s}return t.get_root()}async formFileMetadata(e,t,n,i,r){let s=P(e),o=P(t),c=new TextEncoder().encode(n);return await x(),new W(s,o,c,r,i)}async computeFileKey(e){return await x(),e.getFileKey()}};import{ensure0xPrefix as I}from"@storagehub-sdk/core";var A=class extends l{getHealth(e){return this.ctx.http.get("/health",{...e?{signal:e}:{}})}async getInfo(e){let t=await this.ctx.http.get("/info",{...e?{signal:e}:{}});return{client:t.client,version:t.version,mspId:I(t.mspId),multiaddresses:t.multiaddresses,ownerAccount:I(t.ownerAccount),paymentAccount:I(t.paymentAccount),status:t.status,activeSince:t.activeSince,uptime:t.uptime}}getStats(e){return this.ctx.http.get("/stats",{...e?{signal:e}:{}})}getValuePropositions(e){return this.ctx.http.get("/value-props",{...e?{signal:e}:{}})}async getPaymentStreams(e){let t=await this.withAuth();return this.ctx.http.get("/payment_streams",{...t?{headers:t}:{},...e?{signal:e}:{}})}};var B=class a extends l{config;context;auth;buckets;files;info;constructor(e,t,n){let i={config:e,http:t};super(i,n),this.config=e,this.context=i,this.auth=new b(this.context,n),this.buckets=new S(this.context,n),this.files=new F(this.context,n),this.info=new A(this.context,n)}static async connect(e,t){if(!e?.baseUrl)throw new Error("MspClient.connect: baseUrl is required");let n=new _({baseUrl:e.baseUrl,...e.timeoutMs!==void 0&&{timeoutMs:e.timeoutMs},...e.defaultHeaders!==void 0&&{defaultHeaders:e.defaultHeaders},...e.fetchImpl!==void 0&&{fetchImpl:e.fetchImpl}});if(!t)throw new Error("MspClient.connect: sessionProvider is required");return new a(e,n,t)}};export{B as MspClient};
|
|
2
2
|
//# sourceMappingURL=index.node.js.map
|
package/dist/index.node.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/MspClient.ts", "../src/modules/auth.ts", "../src/base.ts", "../src/modules/buckets.ts", "../src/modules/files.ts", "../src/modules/info.ts"],
|
|
4
|
-
"sourcesContent": ["import type { HttpClientConfig } from \"@storagehub-sdk/core\";\nimport { HttpClient } from \"@storagehub-sdk/core\";\nimport type { MspClientContext } from \"./context.js\";\nimport { AuthModule } from \"./modules/auth.js\";\nimport { BucketsModule } from \"./modules/buckets.js\";\nimport { ModuleBase } from \"./base.js\";\nimport { FilesModule } from \"./modules/files.js\";\nimport { InfoModule } from \"./modules/info.js\";\nimport type { SessionProvider } from \"./types.js\";\n\nexport class MspClient extends ModuleBase {\n public readonly config: HttpClientConfig;\n private readonly context: MspClientContext;\n public readonly auth: AuthModule;\n public readonly buckets: BucketsModule;\n public readonly files: FilesModule;\n public readonly info: InfoModule;\n\n private constructor(\n config: HttpClientConfig,\n http: HttpClient,\n sessionProvider: SessionProvider\n ) {\n const context: MspClientContext = { config, http };\n super(context, sessionProvider);\n this.config = config;\n this.context = context;\n this.auth = new AuthModule(this.context, sessionProvider);\n this.buckets = new BucketsModule(this.context, sessionProvider);\n this.files = new FilesModule(this.context, sessionProvider);\n this.info = new InfoModule(this.context, sessionProvider);\n }\n\n static async connect(\n config: HttpClientConfig,\n sessionProvider: SessionProvider\n ): Promise<MspClient> {\n if (!config?.baseUrl) throw new Error(\"MspClient.connect: baseUrl is required\");\n\n const http = new HttpClient({\n baseUrl: config.baseUrl,\n ...(config.timeoutMs !== undefined && { timeoutMs: config.timeoutMs }),\n ...(config.defaultHeaders !== undefined && {\n defaultHeaders: config.defaultHeaders\n }),\n ...(config.fetchImpl !== undefined && { fetchImpl: config.fetchImpl })\n });\n\n if (!sessionProvider) throw new Error(\"MspClient.connect: sessionProvider is required\");\n return new MspClient(config, http, sessionProvider);\n }\n}\n", "import type { NonceResponse, Session, UserInfo } from \"../types.js\";\nimport { getAddress, type WalletClient } from \"viem\";\nimport { ModuleBase } from \"../base.js\";\n\nconst DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS = 10;\nconst DEFAULT_SIWE_VERIFY_BACKOFF_MS = 100;\n\nexport class AuthModule extends ModuleBase {\n /**\n * Request nonce for SIWE.\n * - Input: EVM `address`, `chainId`.\n * - Output: message to sign.\n */\n private getNonce(address: string, chainId: number, signal?: AbortSignal): Promise<NonceResponse> {\n return this.ctx.http.post<NonceResponse>(\"/auth/nonce\", {\n body: { address, chainId },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n }\n\n /**\n * Verify SIWE signature.\n * - Persists `session` in context on success.\n */\n private async verify(message: string, signature: string, signal?: AbortSignal): Promise<Session> {\n const session = await this.ctx.http.post<Session>(\"/auth/verify\", {\n body: { message, signature },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n\n return session;\n }\n\n /**\n * Full SIWE flow using a `WalletClient`.\n * - Derives address, fetches nonce, signs message, verifies and stores session.\n */\n async SIWE(\n wallet: WalletClient,\n retry = DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS,\n signal?: AbortSignal\n ): Promise<Session> {\n // Resolve the current active account from the WalletClient.\n // - Browser wallets (e.g., MetaMask) surface the user-selected address here.\n // - Viem/local wallets must set `wallet.account` explicitly before calling.\n const account = wallet.account;\n const resolvedAddress = typeof account === \"string\" ? account : account?.address;\n if (!resolvedAddress || !account) {\n throw new Error(\n \"Wallet client has no active account; set wallet.account before calling SIWE\"\n );\n }\n // Get the checksummed address\n const address = getAddress(resolvedAddress);\n const chainId = await wallet.getChainId();\n const { message } = await this.getNonce(address, chainId, signal);\n\n // Sign using the active account resolved above (string or Account object)\n const signature = await wallet.signMessage({ account, message });\n\n // TODO: remove the retry logic once the backend is fixed.\n let lastError: unknown;\n for (let attemptIndex = 0; attemptIndex < retry; attemptIndex++) {\n try {\n return await this.verify(message, signature, signal);\n } catch (err) {\n lastError = err;\n await this.delay(DEFAULT_SIWE_VERIFY_BACKOFF_MS);\n }\n }\n throw lastError instanceof Error ? lastError : new Error(\"SIWE verification failed\");\n }\n\n private async delay(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Fetch authenticated user's profile.\n * - Requires valid `session` (Authorization header added automatically).\n */\n async getProfile(signal?: AbortSignal): Promise<UserInfo> {\n const headers = await this.withAuth();\n return this.ctx.http.get<UserInfo>(\"/auth/profile\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n}\n", "import type { MspClientContext } from \"./context.js\";\nimport type { SessionProvider } from \"./types.js\";\n\nexport abstract class ModuleBase {\n protected readonly ctx: MspClientContext;\n private readonly sessionProvider: SessionProvider;\n\n constructor(ctx: MspClientContext, sessionProvider: SessionProvider) {\n this.ctx = ctx;\n this.sessionProvider = sessionProvider;\n }\n\n protected async withAuth(\n headers?: Record<string, string>\n ): Promise<Record<string, string> | undefined> {\n const session = await this.sessionProvider();\n const token = session?.token;\n if (!token) return headers;\n return headers\n ? { ...headers, Authorization: `Bearer ${token}` }\n : { Authorization: `Bearer ${token}` };\n }\n\n /**\n * Normalize a user-provided path for HTTP query usage.\n * - Removes all leading '/' characters to avoid double slashes in URLs.\n * - Collapses any repeated slashes in the middle or at the end to a single '/'.\n * Examples:\n * \"/foo/bar\" -> \"foo/bar\"\n * \"///docs\" -> \"docs\"\n * \"foo//bar\" -> \"foo/bar\"\n * \"///a//b///\" -> \"a/b/\"\n * \"foo/bar\" -> \"foo/bar\" (unchanged)\n * \"/\" -> \"\"\n */\n protected normalizePath(path: string): string {\n // Drop leading slashes (offset === 0), collapse others to '/'\n return path.replace(/^\\/+|\\/{2,}/g, (_m, offset: number) => (offset === 0 ? \"\" : \"/\"));\n }\n}\n", "import type { Bucket, FileListResponse, GetFilesOptions } from \"../types.js\";\nimport { ModuleBase } from \"../base.js\";\n\nexport class BucketsModule extends ModuleBase {\n /** List all buckets for the current authenticated user */\n async listBuckets(signal?: AbortSignal): Promise<Bucket[]> {\n const headers = await this.withAuth();\n return this.ctx.http.get<Bucket[]>(\"/buckets\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get a specific bucket's metadata by its bucket ID */\n async getBucket(bucketId: string, signal?: AbortSignal): Promise<Bucket> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}`;\n return this.ctx.http.get<Bucket>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n\n /** List files/folders under a path for a bucket (root if no path) */\n async getFiles(bucketId: string, options?: GetFilesOptions): Promise<FileListResponse> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/files`;\n return this.ctx.http.get<FileListResponse>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n ...(options?.path ? { query: { path: this.normalizePath(options.path) } } : {})\n });\n }\n}\n", "import { ModuleBase } from \"../base.js\";\nimport type {\n DownloadOptions,\n DownloadResult,\n FileInfo,\n UploadOptions,\n UploadReceipt\n} from \"../types.js\";\nimport { FileMetadata, FileTrie, initWasm } from \"@storagehub-sdk/core\";\n\nexport class FilesModule extends ModuleBase {\n /** Get metadata for a file in a bucket by fileKey */\n async getFileInfo(bucketId: string, fileKey: string, signal?: AbortSignal): Promise<FileInfo> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/info/${encodeURIComponent(fileKey)}`;\n type FileInfoWire = Omit<FileInfo, \"uploadedAt\"> & { uploadedAt: string };\n const wire = await this.ctx.http.get<FileInfoWire>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n return {\n ...wire,\n uploadedAt: new Date(wire.uploadedAt)\n };\n }\n\n /** Upload a file to a bucket with a specific key */\n async uploadFile(\n bucketId: string,\n fileKey: string,\n file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown,\n owner: string,\n location: string,\n _options?: UploadOptions\n ): Promise<UploadReceipt> {\n void _options;\n\n await initWasm();\n\n const backendPath = `/buckets/${encodeURIComponent(bucketId)}/upload/${encodeURIComponent(fileKey)}`;\n const authHeaders = await this.withAuth();\n\n // Convert the file to a blob and get its size\n const fileBlob = await this.coerceToFormPart(file);\n const fileSize = fileBlob.size;\n\n // Compute the fingerprint first\n const fingerprint = await this.computeFileFingerprint(fileBlob);\n\n // Create the FileMetadata instance\n const metadata = await this.formFileMetadata(\n owner,\n bucketId,\n location,\n fingerprint,\n BigInt(fileSize)\n );\n\n // Compute the file key and ensure it matches the provided file key\n const computedFileKey = await this.computeFileKey(metadata);\n const expectedFileKeyBytes = this.hexToBytes(fileKey);\n if (\n computedFileKey.length !== expectedFileKeyBytes.length ||\n !computedFileKey.every((byte, index) => byte === expectedFileKeyBytes[index])\n ) {\n throw new Error(\n `Computed file key ${computedFileKey.toString()} does not match provided file key ${expectedFileKeyBytes.toString()}`\n );\n }\n\n // Encode the file metadata\n const encodedMetadata = metadata.encode();\n\n // Create the multipart form with both the file and its metadata\n const form = new FormData();\n const fileMetadataBlob = new Blob([new Uint8Array(encodedMetadata)], {\n type: \"application/octet-stream\"\n });\n form.append(\"file_metadata\", fileMetadataBlob, \"file_metadata\");\n form.append(\"file\", fileBlob, \"file\");\n\n const res = await this.ctx.http.put<UploadReceipt>(\n backendPath,\n authHeaders\n ? { body: form as unknown as BodyInit, headers: authHeaders }\n : { body: form as unknown as BodyInit }\n );\n return res;\n }\n\n /** Download a file by key */\n async downloadFile(fileKey: string, options?: DownloadOptions): Promise<DownloadResult> {\n const path = `/download/${encodeURIComponent(fileKey)}`;\n const baseHeaders: Record<string, string> = { Accept: \"*/*\" };\n if (options?.range) {\n const { start, end } = options.range;\n const rangeValue = `bytes=${start}-${end ?? \"\"}`;\n baseHeaders.Range = rangeValue;\n }\n\n const headers = await this.withAuth(baseHeaders);\n\n try {\n const res = await this.ctx.http.getRaw(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {})\n });\n\n if (!res.body) {\n throw new Error(\"Response body is null - unable to create stream\");\n }\n\n const contentType = res.headers.get(\"content-type\");\n const contentRange = res.headers.get(\"content-range\");\n const contentLengthHeader = res.headers.get(\"content-length\");\n const parsedLength = contentLengthHeader !== null ? Number(contentLengthHeader) : undefined;\n const contentLength =\n typeof parsedLength === \"number\" && Number.isFinite(parsedLength) ? parsedLength : null;\n\n return {\n stream: res.body,\n status: res.status,\n contentType,\n contentRange,\n contentLength\n };\n } catch (error) {\n // Handle HTTP errors by returning them as a DownloadResult with the error status\n if (\n error &&\n typeof error === \"object\" &&\n \"status\" in error &&\n typeof error.status === \"number\"\n ) {\n // Create an empty stream for error responses\n const emptyStream = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n }\n });\n\n return {\n stream: emptyStream,\n status: error.status,\n contentType: null,\n contentRange: null,\n contentLength: null\n };\n }\n // Re-throw non-HTTP errors\n throw error;\n }\n }\n\n // Helpers\n private async coerceToFormPart(\n file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown\n ): Promise<Blob> {\n if (typeof Blob !== \"undefined\" && file instanceof Blob) return file;\n if (file instanceof Uint8Array) return new Blob([file.buffer as ArrayBuffer]);\n if (typeof ArrayBuffer !== \"undefined\" && file instanceof ArrayBuffer) return new Blob([file]);\n\n // Handle ReadableStream by reading it into memory\n if (file instanceof ReadableStream) {\n const reader = file.getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n totalLength += value.length;\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n // Combine all chunks into a single Uint8Array\n const combined = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n return new Blob([combined], { type: \"application/octet-stream\" });\n }\n\n return new Blob([file as BlobPart], { type: \"application/octet-stream\" });\n }\n\n private async computeFileFingerprint(fileBlob: Blob): Promise<Uint8Array> {\n const trie = new FileTrie();\n const fileBytes = new Uint8Array(await fileBlob.arrayBuffer());\n\n // Process the file in 1KB chunks (matching CHUNK_SIZE from constants)\n const CHUNK_SIZE = 1024;\n let offset = 0;\n\n while (offset < fileBytes.length) {\n const end = Math.min(offset + CHUNK_SIZE, fileBytes.length);\n const chunk = fileBytes.slice(offset, end);\n trie.push_chunk(chunk);\n offset = end;\n }\n\n return trie.get_root();\n }\n\n private async formFileMetadata(\n owner: string,\n bucketId: string,\n location: string,\n fingerprint: Uint8Array,\n size: bigint\n ): Promise<FileMetadata> {\n const ownerBytes = this.hexToBytes(owner);\n const bucketIdBytes = this.hexToBytes(bucketId);\n const locationBytes = new TextEncoder().encode(location);\n await initWasm();\n return new FileMetadata(ownerBytes, bucketIdBytes, locationBytes, size, fingerprint);\n }\n\n private hexToBytes(hex: string): Uint8Array {\n if (!hex) {\n throw new Error(\"hex string cannot be empty\");\n }\n\n const cleanHex = hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n\n if (cleanHex.length % 2 !== 0) {\n throw new Error(\"hex string must have an even number of characters\");\n }\n\n if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {\n throw new Error(\"hex string contains invalid characters\");\n }\n\n return new Uint8Array(cleanHex.match(/.{2}/g)?.map((byte) => Number.parseInt(byte, 16)) || []);\n }\n\n private async computeFileKey(fileMetadata: FileMetadata): Promise<Uint8Array> {\n await initWasm();\n return fileMetadata.getFileKey();\n }\n}\n", "import { ModuleBase } from \"../base.js\";\nimport type {\n HealthStatus,\n InfoResponse,\n PaymentStreamsResponse,\n StatsResponse,\n ValueProp\n} from \"../types.js\";\n\nexport class InfoModule extends ModuleBase {\n getHealth(signal?: AbortSignal): Promise<HealthStatus> {\n return this.ctx.http.get<HealthStatus>(\"/health\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get general MSP information */\n getInfo(signal?: AbortSignal): Promise<InfoResponse> {\n return this.ctx.http.get<InfoResponse>(\"/info\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get MSP statistics */\n getStats(signal?: AbortSignal): Promise<StatsResponse> {\n return this.ctx.http.get<StatsResponse>(\"/stats\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get available value propositions */\n getValuePropositions(signal?: AbortSignal): Promise<ValueProp[]> {\n return this.ctx.http.get<ValueProp[]>(\"/value-props\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get payment streams for current authenticated user */\n async getPaymentStreams(signal?: AbortSignal): Promise<PaymentStreamsResponse> {\n const headers = await this.withAuth();\n return this.ctx.http.get<PaymentStreamsResponse>(\"/payment_streams\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n}\n"],
|
|
5
|
-
"mappings": "AACA,OAAS,cAAAA,MAAkB,uBCA3B,OAAS,cAAAC,MAAqC,OCEvC,IAAeC,EAAf,KAA0B,CACZ,IACF,gBAEjB,YAAYC,EAAuBC,EAAkC,CACnE,KAAK,IAAMD,EACX,KAAK,gBAAkBC,CACzB,CAEA,MAAgB,SACdC,EAC6C,CAE7C,IAAMC,GADU,MAAM,KAAK,gBAAgB,IACpB,MACvB,OAAKA,EACED,EACH,CAAE,GAAGA,EAAS,cAAe,UAAUC,CAAK,EAAG,EAC/C,CAAE,cAAe,UAAUA,CAAK,EAAG,EAHpBD,CAIrB,CAcU,cAAcE,EAAsB,CAE5C,OAAOA,EAAK,QAAQ,eAAgB,CAACC,EAAIC,IAAoBA,IAAW,EAAI,GAAK,GAAI,CACvF,CACF,EDnCA,IAAMC,EAAoC,GACpCC,EAAiC,IAE1BC,EAAN,cAAyBC,CAAW,
|
|
6
|
-
"names": ["HttpClient", "getAddress", "ModuleBase", "ctx", "sessionProvider", "headers", "token", "path", "_m", "offset", "DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS", "DEFAULT_SIWE_VERIFY_BACKOFF_MS", "AuthModule", "ModuleBase", "address", "chainId", "signal", "message", "signature", "wallet", "retry", "account", "resolvedAddress", "getAddress", "lastError", "attemptIndex", "err", "ms", "resolve", "headers", "BucketsModule", "ModuleBase", "signal", "headers", "bucketId", "path", "options", "FileMetadata", "FileTrie", "initWasm", "FilesModule", "ModuleBase", "bucketId", "fileKey", "signal", "headers", "path", "wire", "file", "owner", "location", "_options", "backendPath", "authHeaders", "fileBlob", "fileSize", "fingerprint", "metadata", "computedFileKey", "expectedFileKeyBytes", "byte", "index", "encodedMetadata", "form", "fileMetadataBlob", "options", "baseHeaders", "start", "end", "rangeValue", "res", "contentType", "contentRange", "contentLengthHeader", "parsedLength", "contentLength", "error", "controller", "reader", "chunks", "totalLength", "done", "value", "combined", "offset", "chunk", "trie", "fileBytes", "CHUNK_SIZE", "size", "ownerBytes", "bucketIdBytes", "locationBytes", "
|
|
4
|
+
"sourcesContent": ["import type { HttpClientConfig } from \"@storagehub-sdk/core\";\nimport { HttpClient } from \"@storagehub-sdk/core\";\nimport type { MspClientContext } from \"./context.js\";\nimport { AuthModule } from \"./modules/auth.js\";\nimport { BucketsModule } from \"./modules/buckets.js\";\nimport { ModuleBase } from \"./base.js\";\nimport { FilesModule } from \"./modules/files.js\";\nimport { InfoModule } from \"./modules/info.js\";\nimport type { SessionProvider } from \"./types.js\";\n\nexport class MspClient extends ModuleBase {\n public readonly config: HttpClientConfig;\n private readonly context: MspClientContext;\n public readonly auth: AuthModule;\n public readonly buckets: BucketsModule;\n public readonly files: FilesModule;\n public readonly info: InfoModule;\n\n private constructor(\n config: HttpClientConfig,\n http: HttpClient,\n sessionProvider: SessionProvider\n ) {\n const context: MspClientContext = { config, http };\n super(context, sessionProvider);\n this.config = config;\n this.context = context;\n this.auth = new AuthModule(this.context, sessionProvider);\n this.buckets = new BucketsModule(this.context, sessionProvider);\n this.files = new FilesModule(this.context, sessionProvider);\n this.info = new InfoModule(this.context, sessionProvider);\n }\n\n static async connect(\n config: HttpClientConfig,\n sessionProvider: SessionProvider\n ): Promise<MspClient> {\n if (!config?.baseUrl) throw new Error(\"MspClient.connect: baseUrl is required\");\n\n const http = new HttpClient({\n baseUrl: config.baseUrl,\n ...(config.timeoutMs !== undefined && { timeoutMs: config.timeoutMs }),\n ...(config.defaultHeaders !== undefined && {\n defaultHeaders: config.defaultHeaders\n }),\n ...(config.fetchImpl !== undefined && { fetchImpl: config.fetchImpl })\n });\n\n if (!sessionProvider) throw new Error(\"MspClient.connect: sessionProvider is required\");\n return new MspClient(config, http, sessionProvider);\n }\n}\n", "import type { NonceResponse, Session, UserInfo } from \"../types.js\";\nimport { getAddress, type WalletClient } from \"viem\";\nimport { ModuleBase } from \"../base.js\";\n\nconst DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS = 10;\nconst DEFAULT_SIWE_VERIFY_BACKOFF_MS = 100;\n\nexport class AuthModule extends ModuleBase {\n /**\n * Request a nonce (challenge message) for Sign-In with Ethereum (SIWE).\n *\n * **Advanced use only:** Most users should use the `SIWE()` method instead, which handles the complete authentication flow automatically. This method is exposed only for custom authentication flows.\n *\n * **Important:** The challenge message expires after a short time (typically 5 minutes). You must call `verify()` with a valid signature before expiration.\n *\n * @param address - The Ethereum address requesting authentication (checksummed format recommended).\n * @param chainId - The chain ID the user is connected to.\n * @param domain - The domain (host[:port]) for the SIWE message (e.g., \"datahaven.app\" or \"localhost:3000\").\n * @param uri - The full URI of your application (e.g., \"https://datahaven.app\" or \"http://localhost:3000\").\n * @param signal - Optional AbortSignal for request cancellation.\n * @returns A promise resolving to the SIWE challenge message to be signed.\n */\n public getNonce(\n address: string,\n chainId: number,\n domain: string,\n uri: string,\n signal?: AbortSignal\n ): Promise<NonceResponse> {\n return this.ctx.http.post<NonceResponse>(\"/auth/nonce\", {\n body: {\n address,\n chainId,\n domain,\n uri\n },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n }\n\n /**\n * Verify a Sign-In with Ethereum (SIWE) signature.\n *\n * **Advanced use only:** Most users should use the `SIWE()` method instead, which handles the complete authentication flow automatically. This method is exposed only for custom authentication flows.\n *\n * **Important:** You must store the returned Session object and provide it via the `sessionProvider` function passed to `MspClient.connect()`.\n * The session is not automatically persisted - you are responsible for managing session storage and ensuring your `sessionProvider` returns it for subsequent authenticated requests.\n *\n * @param message - The SIWE challenge message received from `getNonce()`.\n * @param signature - The signature of the message signed by the user's wallet.\n * @param signal - Optional AbortSignal for request cancellation.\n * @returns A promise resolving to a Session object that you must store and provide via your sessionProvider.\n */\n public async verify(message: string, signature: string, signal?: AbortSignal): Promise<Session> {\n const session = await this.ctx.http.post<Session>(\"/auth/verify\", {\n body: { message, signature },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n\n return session;\n }\n\n /**\n * Complete Sign-In with Ethereum (SIWE) authentication flow using a `WalletClient`.\n *\n * This is the recommended method for authentication. It handles the complete flow automatically:\n * derives the wallet address, fetches a nonce, prompts the user to sign the message, verifies the signature,\n * and returns a session token.\n *\n * **Important:** You must store the returned Session object and provide it via the `sessionProvider` function\n * passed to `MspClient.connect()`. The session is not automatically persisted - you are responsible for managing\n * session storage and ensuring your `sessionProvider` returns it for subsequent authenticated requests.\n *\n * **Note:** This method includes automatic retry logic for verification requests (default: 10 attempts with 100ms backoff).\n * The retry behavior can be customized via the `retry` parameter.\n *\n * @param wallet - The Viem `WalletClient` instance. Must have an active account set (`wallet.account`).\n * - Browser wallets (e.g., MetaMask) automatically surface the user-selected address.\n * - Viem/local wallets must set `wallet.account` explicitly before calling.\n * @param domain - The domain (host[:port]) for the SIWE message (e.g., \"datahaven.app\" or \"localhost:3000\").\n * @param uri - The full URI of your application (e.g., \"https://datahaven.app\" or \"http://localhost:3000\").\n * @param retry - Number of retry attempts for verification requests (default: 10).\n * @param signal - Optional AbortSignal for request cancellation.\n * @returns A promise resolving to a Session object that you must store and provide via your sessionProvider.\n */\n async SIWE(\n wallet: WalletClient,\n domain: string,\n uri: string,\n retry = DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS,\n signal?: AbortSignal\n ): Promise<Session> {\n // Resolve the current active account from the WalletClient.\n // - Browser wallets (e.g., MetaMask) surface the user-selected address here.\n // - Viem/local wallets must set `wallet.account` explicitly before calling.\n const account = wallet.account;\n const resolvedAddress = typeof account === \"string\" ? account : account?.address;\n if (!resolvedAddress || !account) {\n throw new Error(\n \"Wallet client has no active account; set wallet.account before calling SIWE\"\n );\n }\n // Get the checksummed address\n const address = getAddress(resolvedAddress);\n const chainId = await wallet.getChainId();\n const { message } = await this.getNonce(address, chainId, domain, uri, signal);\n\n // Sign using the active account resolved above (string or Account object)\n const signature = await wallet.signMessage({ account, message });\n\n // TODO: remove the retry logic once the backend is fixed.\n let lastError: unknown;\n for (let attemptIndex = 0; attemptIndex < retry; attemptIndex++) {\n try {\n return await this.verify(message, signature, signal);\n } catch (err) {\n lastError = err;\n await this.delay(DEFAULT_SIWE_VERIFY_BACKOFF_MS);\n }\n }\n throw lastError instanceof Error ? lastError : new Error(\"SIWE verification failed\");\n }\n\n private async delay(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Fetch authenticated user's profile.\n * - Requires valid `session` (Authorization header added automatically).\n */\n async getProfile(signal?: AbortSignal): Promise<UserInfo> {\n const headers = await this.withAuth();\n return this.ctx.http.get<UserInfo>(\"/auth/profile\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n}\n", "import type { MspClientContext } from \"./context.js\";\nimport type { SessionProvider } from \"./types.js\";\n\nexport abstract class ModuleBase {\n protected readonly ctx: MspClientContext;\n private readonly sessionProvider: SessionProvider;\n\n constructor(ctx: MspClientContext, sessionProvider: SessionProvider) {\n this.ctx = ctx;\n this.sessionProvider = sessionProvider;\n }\n\n protected async withAuth(\n headers?: Record<string, string>\n ): Promise<Record<string, string> | undefined> {\n const session = await this.sessionProvider();\n const token = session?.token;\n if (!token) return headers;\n return headers\n ? { ...headers, Authorization: `Bearer ${token}` }\n : { Authorization: `Bearer ${token}` };\n }\n\n /**\n * Normalize a user-provided path for HTTP query usage.\n * - Removes all leading '/' characters to avoid double slashes in URLs.\n * - Collapses any repeated slashes in the middle or at the end to a single '/'.\n * Examples:\n * \"/foo/bar\" -> \"foo/bar\"\n * \"///docs\" -> \"docs\"\n * \"foo//bar\" -> \"foo/bar\"\n * \"///a//b///\" -> \"a/b/\"\n * \"foo/bar\" -> \"foo/bar\" (unchanged)\n * \"/\" -> \"\"\n */\n protected normalizePath(path: string): string {\n // Drop leading slashes (offset === 0), collapse others to '/'\n return path.replace(/^\\/+|\\/{2,}/g, (_m, offset: number) => (offset === 0 ? \"\" : \"/\"));\n }\n}\n", "import type { Bucket, FileListResponse, GetFilesOptions, FileTree, FileStatus } from \"../types.js\";\nimport { ModuleBase } from \"../base.js\";\nimport { ensure0xPrefix, parseDate } from \"@storagehub-sdk/core\";\n\n// Wire types received from backend JSON responses\ntype FileTreeWireFile = {\n name: string;\n type: \"file\";\n sizeBytes: number;\n fileKey: string; // may lack 0x\n status: FileStatus;\n uploadedAt: string; // ISO timestamp\n};\n\ntype FileTreeWireFolder = {\n name: string;\n type: \"folder\";\n children?: readonly FileTreeWire[];\n};\n\ntype FileTreeWire = FileTreeWireFile | FileTreeWireFolder;\n\ntype FileListResponseWire =\n | { bucketId: string; files: readonly FileTreeWire[] }\n | { bucketId: string; tree: FileTreeWireFolder };\n\n/** Recursively fix hex prefixes in FileTree structures */\nfunction fixFileTree(item: FileTreeWire): FileTree {\n if (item.type === \"file\") {\n return {\n name: item.name,\n type: item.type,\n sizeBytes: item.sizeBytes,\n fileKey: ensure0xPrefix(item.fileKey),\n status: item.status,\n uploadedAt: parseDate(item.uploadedAt)\n };\n }\n return {\n name: item.name,\n type: item.type,\n children: (item.children ?? []).map(fixFileTree)\n };\n}\n\nexport class BucketsModule extends ModuleBase {\n /** List all buckets for the current authenticated user */\n async listBuckets(signal?: AbortSignal): Promise<Bucket[]> {\n const headers = await this.withAuth();\n const wire = await this.ctx.http.get<Bucket[]>(\"/buckets\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n\n return wire.map((bucket: Bucket) => ({\n bucketId: ensure0xPrefix(bucket.bucketId),\n name: bucket.name,\n root: ensure0xPrefix(bucket.root),\n isPublic: bucket.isPublic,\n sizeBytes: bucket.sizeBytes,\n valuePropId: ensure0xPrefix(bucket.valuePropId),\n fileCount: bucket.fileCount\n }));\n }\n\n /** Get a specific bucket's metadata by its bucket ID */\n async getBucket(bucketId: string, signal?: AbortSignal): Promise<Bucket> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}`;\n\n const wire = await this.ctx.http.get<Bucket>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n\n return {\n bucketId: ensure0xPrefix(wire.bucketId),\n name: wire.name,\n root: ensure0xPrefix(wire.root),\n isPublic: wire.isPublic,\n sizeBytes: wire.sizeBytes,\n valuePropId: ensure0xPrefix(wire.valuePropId),\n fileCount: wire.fileCount\n };\n }\n\n /** List files/folders under a path for a bucket (root if no path) */\n async getFiles(bucketId: string, options?: GetFilesOptions): Promise<FileListResponse> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/files`;\n\n const wire = await this.ctx.http.get<FileListResponseWire>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n ...(options?.path ? { query: { path: this.normalizePath(options.path) } } : {})\n });\n\n const filesWire: readonly FileTreeWire[] = \"files\" in wire ? wire.files : [wire.tree];\n const files: FileTree[] = filesWire.map(fixFileTree);\n const tree = files[0];\n return {\n bucketId: ensure0xPrefix(wire.bucketId),\n files,\n ...(tree ? { tree } : {})\n } as unknown as FileListResponse;\n }\n}\n", "import {\n ensure0xPrefix,\n FileMetadata,\n FileTrie,\n hexToBytes,\n initWasm,\n parseDate\n} from \"@storagehub-sdk/core\";\nimport { ModuleBase } from \"../base.js\";\nimport type {\n DownloadOptions,\n DownloadResult,\n FileStatus,\n StorageFileInfo,\n UploadOptions,\n UploadReceipt\n} from \"../types.js\";\n\nexport class FilesModule extends ModuleBase {\n /** Get metadata for a file in a bucket by fileKey */\n async getFileInfo(\n bucketId: string,\n fileKey: string,\n signal?: AbortSignal\n ): Promise<StorageFileInfo> {\n const headers = await this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/info/${encodeURIComponent(fileKey)}`;\n\n const wire = await this.ctx.http.get<{\n fileKey: string;\n fingerprint: string;\n bucketId: string;\n location: string;\n size: string; // Backend sends as string to avoid precision loss\n isPublic: boolean;\n uploadedAt: string; // ISO string, not Date object\n status: string;\n blockHash: string; // Block hash where file was created\n txHash?: string; // Optional EVM transaction hash\n }>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n\n return {\n fileKey: ensure0xPrefix(wire.fileKey),\n fingerprint: ensure0xPrefix(wire.fingerprint),\n bucketId: ensure0xPrefix(wire.bucketId),\n location: wire.location,\n size: BigInt(wire.size),\n isPublic: wire.isPublic,\n uploadedAt: parseDate(wire.uploadedAt),\n status: wire.status as FileStatus,\n blockHash: ensure0xPrefix(wire.blockHash),\n ...(wire.txHash ? { txHash: ensure0xPrefix(wire.txHash) } : {})\n };\n }\n\n /** Upload a file to a bucket with a specific key */\n async uploadFile(\n bucketId: string,\n fileKey: string,\n file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown,\n owner: string,\n location: string,\n _options?: UploadOptions\n ): Promise<UploadReceipt> {\n void _options;\n\n await initWasm();\n\n const backendPath = `/buckets/${encodeURIComponent(bucketId)}/upload/${encodeURIComponent(fileKey)}`;\n const authHeaders = await this.withAuth();\n\n // Convert the file to a blob and get its size\n const fileBlob = await this.coerceToFormPart(file);\n const fileSize = fileBlob.size;\n\n // Compute the fingerprint first\n const fingerprint = await this.computeFileFingerprint(fileBlob);\n\n // Create the FileMetadata instance\n const metadata = await this.formFileMetadata(\n owner,\n bucketId,\n location,\n fingerprint,\n BigInt(fileSize)\n );\n\n // Compute the file key and ensure it matches the provided file key\n const computedFileKey = await this.computeFileKey(metadata);\n const expectedFileKeyBytes = hexToBytes(fileKey);\n if (\n computedFileKey.length !== expectedFileKeyBytes.length ||\n !computedFileKey.every((byte, index) => byte === expectedFileKeyBytes[index])\n ) {\n throw new Error(\n `Computed file key ${computedFileKey.toString()} does not match provided file key ${expectedFileKeyBytes.toString()}`\n );\n }\n\n // Encode the file metadata\n const encodedMetadata = metadata.encode();\n\n // Create the multipart form with both the file and its metadata\n const form = new FormData();\n const fileMetadataBlob = new Blob([new Uint8Array(encodedMetadata)], {\n type: \"application/octet-stream\"\n });\n form.append(\"file_metadata\", fileMetadataBlob, \"file_metadata\");\n form.append(\"file\", fileBlob, \"file\");\n\n const res = await this.ctx.http.put<UploadReceipt>(\n backendPath,\n authHeaders\n ? { body: form as unknown as BodyInit, headers: authHeaders }\n : { body: form as unknown as BodyInit }\n );\n return res;\n }\n\n /** Download a file by key */\n async downloadFile(fileKey: string, options?: DownloadOptions): Promise<DownloadResult> {\n const path = `/download/${encodeURIComponent(fileKey)}`;\n const baseHeaders: Record<string, string> = { Accept: \"*/*\" };\n if (options?.range) {\n const { start, end } = options.range;\n const rangeValue = `bytes=${start}-${end ?? \"\"}`;\n baseHeaders.Range = rangeValue;\n }\n\n const headers = await this.withAuth(baseHeaders);\n\n try {\n const res = await this.ctx.http.getRaw(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {})\n });\n\n if (!res.body) {\n throw new Error(\"Response body is null - unable to create stream\");\n }\n\n const contentType = res.headers.get(\"content-type\");\n const contentRange = res.headers.get(\"content-range\");\n const contentLengthHeader = res.headers.get(\"content-length\");\n const parsedLength = contentLengthHeader !== null ? Number(contentLengthHeader) : undefined;\n const contentLength =\n typeof parsedLength === \"number\" && Number.isFinite(parsedLength) ? parsedLength : null;\n\n return {\n stream: res.body,\n status: res.status,\n contentType,\n contentRange,\n contentLength\n };\n } catch (error) {\n // Handle HTTP errors by returning them as a DownloadResult with the error status\n if (this.isHttpError(error)) {\n return {\n stream: this.createEmptyStream(),\n status: error.status,\n contentType: null,\n contentRange: null,\n contentLength: null\n };\n }\n // Re-throw non-HTTP errors\n throw error;\n }\n }\n\n // Helpers\n private isHttpError(error: unknown): error is { status: number } {\n return (\n error !== null &&\n typeof error === \"object\" &&\n \"status\" in error &&\n typeof error.status === \"number\"\n );\n }\n\n private createEmptyStream(): ReadableStream<Uint8Array> {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n }\n });\n }\n\n private async coerceToFormPart(\n file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown\n ): Promise<Blob> {\n if (typeof Blob !== \"undefined\" && file instanceof Blob) return file;\n if (file instanceof Uint8Array) return new Blob([file.buffer as ArrayBuffer]);\n if (typeof ArrayBuffer !== \"undefined\" && file instanceof ArrayBuffer) return new Blob([file]);\n\n // Handle ReadableStream by reading it into memory\n if (file instanceof ReadableStream) {\n const reader = file.getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n totalLength += value.length;\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n // Combine all chunks into a single Uint8Array\n const combined = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n return new Blob([combined], { type: \"application/octet-stream\" });\n }\n\n return new Blob([file as BlobPart], { type: \"application/octet-stream\" });\n }\n\n private async computeFileFingerprint(fileBlob: Blob): Promise<Uint8Array> {\n const trie = new FileTrie();\n const fileBytes = new Uint8Array(await fileBlob.arrayBuffer());\n\n // Process the file in 1KB chunks (matching CHUNK_SIZE from constants)\n const CHUNK_SIZE = 1024;\n let offset = 0;\n\n while (offset < fileBytes.length) {\n const end = Math.min(offset + CHUNK_SIZE, fileBytes.length);\n const chunk = fileBytes.slice(offset, end);\n trie.push_chunk(chunk);\n offset = end;\n }\n\n return trie.get_root();\n }\n\n private async formFileMetadata(\n owner: string,\n bucketId: string,\n location: string,\n fingerprint: Uint8Array,\n size: bigint\n ): Promise<FileMetadata> {\n const ownerBytes = hexToBytes(owner);\n const bucketIdBytes = hexToBytes(bucketId);\n const locationBytes = new TextEncoder().encode(location);\n await initWasm();\n return new FileMetadata(ownerBytes, bucketIdBytes, locationBytes, size, fingerprint);\n }\n\n private async computeFileKey(fileMetadata: FileMetadata): Promise<Uint8Array> {\n await initWasm();\n return fileMetadata.getFileKey();\n }\n}\n", "import { ModuleBase } from \"../base.js\";\nimport type {\n HealthStatus,\n InfoResponse,\n PaymentStreamsResponse,\n StatsResponse,\n ValueProp\n} from \"../types.js\";\nimport { ensure0xPrefix } from \"@storagehub-sdk/core\";\n\nexport class InfoModule extends ModuleBase {\n getHealth(signal?: AbortSignal): Promise<HealthStatus> {\n return this.ctx.http.get<HealthStatus>(\"/health\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get general MSP information */\n async getInfo(signal?: AbortSignal): Promise<InfoResponse> {\n const wire = await this.ctx.http.get<{\n client: string;\n version: string;\n mspId: string;\n multiaddresses: string[];\n ownerAccount: string;\n paymentAccount: string;\n status: string;\n activeSince: number;\n uptime: string;\n }>(\"/info\", {\n ...(signal ? { signal } : {})\n });\n\n return {\n client: wire.client,\n version: wire.version,\n mspId: ensure0xPrefix(wire.mspId),\n multiaddresses: wire.multiaddresses,\n ownerAccount: ensure0xPrefix(wire.ownerAccount), // Ensure 0x prefix (backend has it, but TypeScript needs guarantee)\n paymentAccount: ensure0xPrefix(wire.paymentAccount), // Ensure 0x prefix (backend has it, but TypeScript needs guarantee)\n status: wire.status,\n activeSince: wire.activeSince,\n uptime: wire.uptime\n };\n }\n\n /** Get MSP statistics */\n getStats(signal?: AbortSignal): Promise<StatsResponse> {\n return this.ctx.http.get<StatsResponse>(\"/stats\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get available value propositions */\n getValuePropositions(signal?: AbortSignal): Promise<ValueProp[]> {\n return this.ctx.http.get<ValueProp[]>(\"/value-props\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get payment streams for current authenticated user */\n async getPaymentStreams(signal?: AbortSignal): Promise<PaymentStreamsResponse> {\n const headers = await this.withAuth();\n return this.ctx.http.get<PaymentStreamsResponse>(\"/payment_streams\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["HttpClient", "getAddress", "ModuleBase", "ctx", "sessionProvider", "headers", "token", "path", "_m", "offset", "DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS", "DEFAULT_SIWE_VERIFY_BACKOFF_MS", "AuthModule", "ModuleBase", "address", "chainId", "domain", "uri", "signal", "message", "signature", "wallet", "retry", "account", "resolvedAddress", "getAddress", "lastError", "attemptIndex", "err", "ms", "resolve", "headers", "ensure0xPrefix", "parseDate", "fixFileTree", "item", "BucketsModule", "ModuleBase", "signal", "headers", "bucket", "bucketId", "path", "wire", "options", "files", "tree", "ensure0xPrefix", "FileMetadata", "FileTrie", "hexToBytes", "initWasm", "parseDate", "FilesModule", "ModuleBase", "bucketId", "fileKey", "signal", "headers", "path", "wire", "ensure0xPrefix", "parseDate", "file", "owner", "location", "_options", "initWasm", "backendPath", "authHeaders", "fileBlob", "fileSize", "fingerprint", "metadata", "computedFileKey", "expectedFileKeyBytes", "hexToBytes", "byte", "index", "encodedMetadata", "form", "fileMetadataBlob", "options", "baseHeaders", "start", "end", "rangeValue", "res", "contentType", "contentRange", "contentLengthHeader", "parsedLength", "contentLength", "error", "controller", "reader", "chunks", "totalLength", "done", "value", "combined", "offset", "chunk", "trie", "FileTrie", "fileBytes", "CHUNK_SIZE", "size", "ownerBytes", "bucketIdBytes", "locationBytes", "FileMetadata", "fileMetadata", "ensure0xPrefix", "InfoModule", "ModuleBase", "signal", "wire", "headers", "MspClient", "_MspClient", "ModuleBase", "config", "http", "sessionProvider", "context", "AuthModule", "BucketsModule", "FilesModule", "InfoModule", "HttpClient"]
|
|
7
7
|
}
|
package/dist/modules/auth.d.ts
CHANGED
|
@@ -1,23 +1,60 @@
|
|
|
1
|
-
import type { Session, UserInfo } from "../types.js";
|
|
1
|
+
import type { NonceResponse, Session, UserInfo } from "../types.js";
|
|
2
2
|
import { type WalletClient } from "viem";
|
|
3
3
|
import { ModuleBase } from "../base.js";
|
|
4
4
|
export declare class AuthModule extends ModuleBase {
|
|
5
5
|
/**
|
|
6
|
-
* Request nonce for SIWE.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Request a nonce (challenge message) for Sign-In with Ethereum (SIWE).
|
|
7
|
+
*
|
|
8
|
+
* **Advanced use only:** Most users should use the `SIWE()` method instead, which handles the complete authentication flow automatically. This method is exposed only for custom authentication flows.
|
|
9
|
+
*
|
|
10
|
+
* **Important:** The challenge message expires after a short time (typically 5 minutes). You must call `verify()` with a valid signature before expiration.
|
|
11
|
+
*
|
|
12
|
+
* @param address - The Ethereum address requesting authentication (checksummed format recommended).
|
|
13
|
+
* @param chainId - The chain ID the user is connected to.
|
|
14
|
+
* @param domain - The domain (host[:port]) for the SIWE message (e.g., "datahaven.app" or "localhost:3000").
|
|
15
|
+
* @param uri - The full URI of your application (e.g., "https://datahaven.app" or "http://localhost:3000").
|
|
16
|
+
* @param signal - Optional AbortSignal for request cancellation.
|
|
17
|
+
* @returns A promise resolving to the SIWE challenge message to be signed.
|
|
9
18
|
*/
|
|
10
|
-
|
|
19
|
+
getNonce(address: string, chainId: number, domain: string, uri: string, signal?: AbortSignal): Promise<NonceResponse>;
|
|
11
20
|
/**
|
|
12
|
-
* Verify SIWE signature.
|
|
13
|
-
*
|
|
21
|
+
* Verify a Sign-In with Ethereum (SIWE) signature.
|
|
22
|
+
*
|
|
23
|
+
* **Advanced use only:** Most users should use the `SIWE()` method instead, which handles the complete authentication flow automatically. This method is exposed only for custom authentication flows.
|
|
24
|
+
*
|
|
25
|
+
* **Important:** You must store the returned Session object and provide it via the `sessionProvider` function passed to `MspClient.connect()`.
|
|
26
|
+
* The session is not automatically persisted - you are responsible for managing session storage and ensuring your `sessionProvider` returns it for subsequent authenticated requests.
|
|
27
|
+
*
|
|
28
|
+
* @param message - The SIWE challenge message received from `getNonce()`.
|
|
29
|
+
* @param signature - The signature of the message signed by the user's wallet.
|
|
30
|
+
* @param signal - Optional AbortSignal for request cancellation.
|
|
31
|
+
* @returns A promise resolving to a Session object that you must store and provide via your sessionProvider.
|
|
14
32
|
*/
|
|
15
|
-
|
|
33
|
+
verify(message: string, signature: string, signal?: AbortSignal): Promise<Session>;
|
|
16
34
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
35
|
+
* Complete Sign-In with Ethereum (SIWE) authentication flow using a `WalletClient`.
|
|
36
|
+
*
|
|
37
|
+
* This is the recommended method for authentication. It handles the complete flow automatically:
|
|
38
|
+
* derives the wallet address, fetches a nonce, prompts the user to sign the message, verifies the signature,
|
|
39
|
+
* and returns a session token.
|
|
40
|
+
*
|
|
41
|
+
* **Important:** You must store the returned Session object and provide it via the `sessionProvider` function
|
|
42
|
+
* passed to `MspClient.connect()`. The session is not automatically persisted - you are responsible for managing
|
|
43
|
+
* session storage and ensuring your `sessionProvider` returns it for subsequent authenticated requests.
|
|
44
|
+
*
|
|
45
|
+
* **Note:** This method includes automatic retry logic for verification requests (default: 10 attempts with 100ms backoff).
|
|
46
|
+
* The retry behavior can be customized via the `retry` parameter.
|
|
47
|
+
*
|
|
48
|
+
* @param wallet - The Viem `WalletClient` instance. Must have an active account set (`wallet.account`).
|
|
49
|
+
* - Browser wallets (e.g., MetaMask) automatically surface the user-selected address.
|
|
50
|
+
* - Viem/local wallets must set `wallet.account` explicitly before calling.
|
|
51
|
+
* @param domain - The domain (host[:port]) for the SIWE message (e.g., "datahaven.app" or "localhost:3000").
|
|
52
|
+
* @param uri - The full URI of your application (e.g., "https://datahaven.app" or "http://localhost:3000").
|
|
53
|
+
* @param retry - Number of retry attempts for verification requests (default: 10).
|
|
54
|
+
* @param signal - Optional AbortSignal for request cancellation.
|
|
55
|
+
* @returns A promise resolving to a Session object that you must store and provide via your sessionProvider.
|
|
19
56
|
*/
|
|
20
|
-
SIWE(wallet: WalletClient, retry?: number, signal?: AbortSignal): Promise<Session>;
|
|
57
|
+
SIWE(wallet: WalletClient, domain: string, uri: string, retry?: number, signal?: AbortSignal): Promise<Session>;
|
|
21
58
|
private delay;
|
|
22
59
|
/**
|
|
23
60
|
* Fetch authenticated user's profile.
|
package/dist/modules/files.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { ModuleBase } from "../base.js";
|
|
2
|
-
import type { DownloadOptions, DownloadResult,
|
|
2
|
+
import type { DownloadOptions, DownloadResult, StorageFileInfo, UploadOptions, UploadReceipt } from "../types.js";
|
|
3
3
|
export declare class FilesModule extends ModuleBase {
|
|
4
4
|
/** Get metadata for a file in a bucket by fileKey */
|
|
5
|
-
getFileInfo(bucketId: string, fileKey: string, signal?: AbortSignal): Promise<
|
|
5
|
+
getFileInfo(bucketId: string, fileKey: string, signal?: AbortSignal): Promise<StorageFileInfo>;
|
|
6
6
|
/** Upload a file to a bucket with a specific key */
|
|
7
7
|
uploadFile(bucketId: string, fileKey: string, file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown, owner: string, location: string, _options?: UploadOptions): Promise<UploadReceipt>;
|
|
8
8
|
/** Download a file by key */
|
|
9
9
|
downloadFile(fileKey: string, options?: DownloadOptions): Promise<DownloadResult>;
|
|
10
|
+
private isHttpError;
|
|
11
|
+
private createEmptyStream;
|
|
10
12
|
private coerceToFormPart;
|
|
11
13
|
private computeFileFingerprint;
|
|
12
14
|
private formFileMetadata;
|
|
13
|
-
private hexToBytes;
|
|
14
15
|
private computeFileKey;
|
|
15
16
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FileInfo } from "@storagehub-sdk/core";
|
|
1
2
|
export declare enum HealthState {
|
|
2
3
|
Healthy = "healthy",
|
|
3
4
|
Unhealthy = "unhealthy",
|
|
@@ -95,9 +96,9 @@ export interface DownloadResult {
|
|
|
95
96
|
contentRange?: string | null;
|
|
96
97
|
}
|
|
97
98
|
export interface Bucket {
|
|
98
|
-
bucketId: string
|
|
99
|
+
bucketId: `0x${string}`;
|
|
99
100
|
name: string;
|
|
100
|
-
root: string
|
|
101
|
+
root: `0x${string}`;
|
|
101
102
|
isPublic: boolean;
|
|
102
103
|
sizeBytes: number;
|
|
103
104
|
valuePropId: string;
|
|
@@ -109,18 +110,16 @@ export type FileTree = {
|
|
|
109
110
|
} & ({
|
|
110
111
|
type: "file";
|
|
111
112
|
sizeBytes: number;
|
|
112
|
-
fileKey: string
|
|
113
|
+
fileKey: `0x${string}`;
|
|
113
114
|
status: FileStatus;
|
|
115
|
+
uploadedAt: Date;
|
|
114
116
|
} | {
|
|
115
117
|
type: "folder";
|
|
116
|
-
});
|
|
117
|
-
export type FileTreeRoot = {
|
|
118
|
-
name: string;
|
|
119
118
|
children: FileTree[];
|
|
120
|
-
};
|
|
119
|
+
});
|
|
121
120
|
export interface FileListResponse {
|
|
122
|
-
bucketId: string
|
|
123
|
-
|
|
121
|
+
bucketId: `0x${string}`;
|
|
122
|
+
files: FileTree[];
|
|
124
123
|
}
|
|
125
124
|
export interface GetFilesOptions {
|
|
126
125
|
path?: string;
|
|
@@ -129,10 +128,10 @@ export interface GetFilesOptions {
|
|
|
129
128
|
export interface InfoResponse {
|
|
130
129
|
client: string;
|
|
131
130
|
version: string;
|
|
132
|
-
mspId: string
|
|
131
|
+
mspId: `0x${string}`;
|
|
133
132
|
multiaddresses: string[];
|
|
134
|
-
ownerAccount: string
|
|
135
|
-
paymentAccount: string
|
|
133
|
+
ownerAccount: `0x${string}`;
|
|
134
|
+
paymentAccount: `0x${string}`;
|
|
136
135
|
status: string;
|
|
137
136
|
activeSince: number;
|
|
138
137
|
uptime: string;
|
|
@@ -155,13 +154,7 @@ export interface ValueProp {
|
|
|
155
154
|
dataLimitPerBucketBytes: number;
|
|
156
155
|
isAvailable: boolean;
|
|
157
156
|
}
|
|
158
|
-
export interface FileInfo {
|
|
159
|
-
fileKey: string;
|
|
160
|
-
fingerprint: string;
|
|
161
|
-
bucketId: string;
|
|
162
|
-
name: string;
|
|
163
|
-
location: string;
|
|
164
|
-
size: number;
|
|
157
|
+
export interface StorageFileInfo extends FileInfo {
|
|
165
158
|
isPublic: boolean;
|
|
166
159
|
uploadedAt: Date;
|
|
167
160
|
status: FileStatus;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storagehub-sdk/msp-client",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "High-level TypeScript client for StorageHub MSP: simple file upload/download, SIWE-style auth, health checks, and bucket browsing built on top of @storagehub-sdk/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.node.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"viem": ">=2.38.3",
|
|
31
|
-
"@storagehub-sdk/core": "0.3.
|
|
31
|
+
"@storagehub-sdk/core": "0.3.3"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=22"
|