@storagehub-sdk/msp-client 0.3.4 → 0.3.5-dev.1
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/MspClient.d.ts +8 -1
- package/dist/base.d.ts +9 -2
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +3 -3
- package/dist/index.node.js +1 -1
- package/dist/index.node.js.map +3 -3
- package/dist/modules/auth.d.ts +73 -3
- package/package.json +4 -4
package/dist/MspClient.d.ts
CHANGED
|
@@ -13,5 +13,12 @@ export declare class MspClient extends ModuleBase {
|
|
|
13
13
|
readonly files: FilesModule;
|
|
14
14
|
readonly info: InfoModule;
|
|
15
15
|
private constructor();
|
|
16
|
-
static connect(config: HttpClientConfig, sessionProvider
|
|
16
|
+
static connect(config: HttpClientConfig, sessionProvider?: SessionProvider): Promise<MspClient>;
|
|
17
|
+
/**
|
|
18
|
+
* Updates the session provider for this client and all its modules.
|
|
19
|
+
* This allows updating authentication after the client has been created.
|
|
20
|
+
*
|
|
21
|
+
* @param sessionProvider - The new session provider function.
|
|
22
|
+
*/
|
|
23
|
+
setSessionProvider(sessionProvider: SessionProvider): void;
|
|
17
24
|
}
|
package/dist/base.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { MspClientContext } from "./context.js";
|
|
2
2
|
import type { SessionProvider } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Shared reference to sessionProvider so all modules use the same instance.
|
|
5
|
+
*/
|
|
6
|
+
type SessionProviderRef = {
|
|
7
|
+
current: SessionProvider;
|
|
8
|
+
};
|
|
3
9
|
export declare abstract class ModuleBase {
|
|
4
10
|
protected readonly ctx: MspClientContext;
|
|
5
|
-
|
|
6
|
-
constructor(ctx: MspClientContext,
|
|
11
|
+
protected readonly sessionProviderRef: SessionProviderRef;
|
|
12
|
+
constructor(ctx: MspClientContext, sessionProviderRef: SessionProviderRef);
|
|
7
13
|
protected withAuth(headers?: Record<string, string>): Promise<Record<string, string> | undefined>;
|
|
8
14
|
/**
|
|
9
15
|
* Normalize a user-provided path for HTTP query usage.
|
|
@@ -19,3 +25,4 @@ export declare abstract class ModuleBase {
|
|
|
19
25
|
*/
|
|
20
26
|
protected normalizePath(path: string): string;
|
|
21
27
|
}
|
|
28
|
+
export {};
|
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;sessionProviderRef;constructor(e,t){this.ctx=e,this.sessionProviderRef=t}async withAuth(e){let n=(await this.sessionProviderRef.current())?.token;return n?e?{...e,Authorization:`Bearer ${n}`}:{Authorization:`Bearer ${n}`}:e}normalizePath(e){return e.replace(/^\/+|\/{2,}/g,(t,n)=>n===0?"":"/")}};var B=10,T=100,y=class extends l{getNonce(e,t,n,s,i){return this.ctx.http.post("/auth/nonce",{body:{address:e,chainId:t,domain:n,uri:s},headers:{"Content-Type":"application/json"},...i?{signal:i}:{}})}getMessage(e,t,n,s){return this.ctx.http.post("/auth/message",{body:{address:e,chainId:t,uri:n},headers:{"Content-Type":"application/json"},...s?{signal:s}:{}})}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,s=B,i){let{account:r,address:o,chainId:a}=await this.resolveAccount(e,"SIWE"),{message:p}=await this.getNonce(o,a,t,n,i);return this.signAndVerifyWithRetry(e,r,p,s,i,"SIWE")}async SIWX(e,t,n=B,s){let{account:i,address:r,chainId:o}=await this.resolveAccount(e,"SIWX"),{message:a}=await this.getMessage(r,o,t,s);return this.signAndVerifyWithRetry(e,i,a,n,s,"SIWX")}async resolveAccount(e,t){let n=e.account,s=typeof n=="string"?n:n?.address;if(!s||!n)throw new Error(`Wallet client has no active account; set wallet.account before calling ${t}`);let i=M(s),r=await e.getChainId();return{account:n,address:i,chainId:r}}async signAndVerifyWithRetry(e,t,n,s,i,r){let o=await e.signMessage({account:t,message:n}),a;for(let p=0;p<s;p++)try{return await this.verify(n,o,i)}catch(d){a=d,await this.delay(T)}throw a instanceof Error?a:new Error(`${r} 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 u,parseDate as H}from"@storagehub-sdk/core";function C(c){return c.type==="file"?{name:c.name,type:c.type,sizeBytes:c.sizeBytes,fileKey:u(c.fileKey),status:c.status,uploadedAt:H(c.uploadedAt)}:{name:c.name,type:c.type,children:(c.children??[]).map(C)}}var f=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(s=>({bucketId:u(s.bucketId),name:s.name,root:u(s.root),isPublic:s.isPublic,sizeBytes:s.sizeBytes,valuePropId:u(s.valuePropId),fileCount:s.fileCount}))}async getBucket(e,t){let n=await this.withAuth(),s=`/buckets/${encodeURIComponent(e)}`,i=await this.ctx.http.get(s,{...n?{headers:n}:{},...t?{signal:t}:{}});return{bucketId:u(i.bucketId),name:i.name,root:u(i.root),isPublic:i.isPublic,sizeBytes:i.sizeBytes,valuePropId:u(i.valuePropId),fileCount:i.fileCount}}async getFiles(e,t){let n=await this.withAuth(),s=`/buckets/${encodeURIComponent(e)}/files`,i=await this.ctx.http.get(s,{...n?{headers:n}:{},...t?.signal?{signal:t.signal}:{},...t?.path?{query:{path:this.normalizePath(t.path)}}:{}}),o=("files"in i?i.files:[i.tree]).map(C),a=o[0];return{bucketId:u(i.bucketId),files:o,...a?{tree:a}:{}}}};import{ensure0xPrefix as h,FileMetadata as E,FileTrie as z,hexToBytes as P,initWasm as F,parseDate as N}from"@storagehub-sdk/core";var g=class extends l{async getFileInfo(e,t,n){let s=await this.withAuth(),i=`/buckets/${encodeURIComponent(e)}/info/${encodeURIComponent(t)}`,r=await this.ctx.http.get(i,{...s?{headers:s}:{},...n?{signal:n}:{}});return{fileKey:h(r.fileKey),fingerprint:h(r.fingerprint),bucketId:h(r.bucketId),location:r.location,size:BigInt(r.size),isPublic:r.isPublic,uploadedAt:N(r.uploadedAt),status:r.status,blockHash:h(r.blockHash),...r.txHash?{txHash:h(r.txHash)}:{}}}async uploadFile(e,t,n,s,i,r){await F();let o=`/buckets/${encodeURIComponent(e)}/upload/${encodeURIComponent(t)}`,a=await this.withAuth(),p=await this.coerceToFormPart(n),d=p.size,w=await this.computeFileFingerprint(p),x=await this.formFileMetadata(s,e,i,w,BigInt(d)),S=await this.computeFileKey(x),A=P(t);if(S.length!==A.length||!S.every((U,W)=>U===A[W]))throw new Error(`Computed file key ${S.toString()} does not match provided file key ${A.toString()}`);let v=x.encode(),m=new FormData,k=new Blob([new Uint8Array(v)],{type:"application/octet-stream"});return m.append("file_metadata",k,"file_metadata"),m.append("file",p,"file"),await this.ctx.http.put(o,a?{body:m,headers:a}:{body:m})}async downloadFile(e,t){let n=`/download/${encodeURIComponent(e)}`,s={Accept:"*/*"};if(t?.range){let{start:r,end:o}=t.range,a=`bytes=${r}-${o??""}`;s.Range=a}let i=await this.withAuth(s);try{let r=await this.ctx.http.getRaw(n,{...i?{headers:i}:{},...t?.signal?{signal:t.signal}:{}});if(!r.body)throw new Error("Response body is null - unable to create stream");let o=r.headers.get("content-type"),a=r.headers.get("content-range"),p=r.headers.get("content-length"),d=p!==null?Number(p):void 0,w=typeof d=="number"&&Number.isFinite(d)?d:null;return{stream:r.body,status:r.status,contentType:o,contentRange:a,contentLength:w}}catch(r){if(this.isHttpError(r))return{stream:this.createEmptyStream(),status:r.status,contentType:null,contentRange:null,contentLength:null};throw r}}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=[],s=0;try{for(;;){let{done:o,value:a}=await t.read();if(o)break;a&&(n.push(a),s+=a.length)}}finally{t.releaseLock()}let i=new Uint8Array(s),r=0;for(let o of n)i.set(o,r),r+=o.length;return new Blob([i],{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()),s=1024,i=0;for(;i<n.length;){let r=Math.min(i+s,n.length),o=n.slice(i,r);t.push_chunk(o),i=r}return t.get_root()}async formFileMetadata(e,t,n,s,i){let r=P(e),o=P(t),a=new TextEncoder().encode(n);return await F(),new E(r,o,a,i,s)}async computeFileKey(e){return await F(),e.getFileKey()}};import{ensure0xPrefix as I}from"@storagehub-sdk/core";var b=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 R=class c extends l{config;context;auth;buckets;files;info;constructor(e,t,n){let s={config:e,http:t};super(s,n),this.config=e,this.context=s,this.auth=new y(this.context,n),this.buckets=new f(this.context,n),this.files=new g(this.context,n),this.info=new b(this.context,n)}static async connect(e,t=async()=>{}){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}}),s={current:t};return new c(e,n,s)}setSessionProvider(e){this.sessionProviderRef.current=e}};export{R 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 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": "AACA,OAAS,cAAAA,MAAkB,uBCA3B,OAAS,cAAAC,MAAqC,
|
|
6
|
-
"names": ["HttpClient", "getAddress", "ModuleBase", "ctx", "
|
|
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 sessionProviderRef: { current: SessionProvider }\n ) {\n const context: MspClientContext = { config, http };\n super(context, sessionProviderRef);\n this.config = config;\n this.context = context;\n this.auth = new AuthModule(this.context, sessionProviderRef);\n this.buckets = new BucketsModule(this.context, sessionProviderRef);\n this.files = new FilesModule(this.context, sessionProviderRef);\n this.info = new InfoModule(this.context, sessionProviderRef);\n }\n\n static async connect(\n config: HttpClientConfig,\n sessionProvider: SessionProvider = async () => undefined\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 // Create a shared reference object\n const sessionProviderRef = { current: sessionProvider };\n return new MspClient(config, http, sessionProviderRef);\n }\n\n /**\n * Updates the session provider for this client and all its modules.\n * This allows updating authentication after the client has been created.\n *\n * @param sessionProvider - The new session provider function.\n */\n setSessionProvider(sessionProvider: SessionProvider): void {\n this.sessionProviderRef.current = 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 * Request a message (challenge) for Sign-In with X (SIWX) using CAIP-122 standard.\n *\n * **Advanced use only:** Most users should use the `SIWX()` method instead, which handles the complete authentication flow automatically. This method is exposed only for custom authentication flows.\n *\n * This method follows the CAIP-122 standard for chain-agnostic authentication.\n * The message uses CAIP-10 format for addresses (e.g., `eip155:55931:0x...`).\n *\n * **Important:** The challenge message expires after a short time (typically 5 minutes).\n * You must call `verify()` with a valid signature before expiration.\n *\n * **Note:** According to CAIP-122, the domain is extracted from the URI automatically.\n * You do not need to provide the domain separately - it will be extracted from the URI.\n *\n * @param address - The blockchain address requesting authentication (checksummed format recommended).\n * @param chainId - The chain ID the user is connected to.\n * @param uri - The full URI of your dApp (e.g., \"https://datahaven.app\"). This should be the dApp URL, not the MSP API URL.\n * The domain will be automatically extracted from this URI per CAIP-122 specification.\n * @param signal - Optional AbortSignal for request cancellation.\n * @returns A promise resolving to the CAIP-122 challenge message to be signed.\n */\n public getMessage(\n address: string,\n chainId: number,\n uri: string,\n signal?: AbortSignal\n ): Promise<NonceResponse> {\n return this.ctx.http.post<NonceResponse>(\"/auth/message\", {\n body: {\n address,\n chainId,\n uri\n },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n }\n\n /**\n * Verify a Sign-In signature (works with both SIWE and SIWX/CAIP-122 messages).\n *\n * **Advanced use only:** Most users should use the `SIWE()` or `SIWX()` methods instead, which handle 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 challenge message received from `getNonce()` (SIWE) or `getMessage()` (CAIP-122).\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 const { account, address, chainId } = await this.resolveAccount(wallet, \"SIWE\");\n const { message } = await this.getNonce(address, chainId, domain, uri, signal);\n return this.signAndVerifyWithRetry(wallet, account, message, retry, signal, \"SIWE\");\n }\n\n /**\n * Complete Sign-In with X (SIWX) authentication flow using CAIP-122 standard and a `WalletClient`.\n *\n * This is the recommended method for CAIP-122 authentication. It handles the complete flow automatically:\n * derives the wallet address, fetches a CAIP-122 message, 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 * **CAIP-122:** Unlike SIWE, this method does not require a `domain` parameter. The domain is automatically extracted\n * from the `uri` parameter on the backend per CAIP-122 specification.\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 uri - The full URI of your dApp (e.g., \"https://datahaven.app\"). This should be the dApp URL, not the MSP API URL.\n * The domain will be automatically extracted from this URI per CAIP-122 specification.\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 SIWX(\n wallet: WalletClient,\n uri: string,\n retry = DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS,\n signal?: AbortSignal\n ): Promise<Session> {\n const { account, address, chainId } = await this.resolveAccount(wallet, \"SIWX\");\n const { message } = await this.getMessage(address, chainId, uri, signal);\n return this.signAndVerifyWithRetry(wallet, account, message, retry, signal, \"SIWX\");\n }\n\n /**\n * Resolves and validates the account from a WalletClient.\n *\n * @param wallet - The Viem WalletClient instance.\n * @param methodName - The name of the calling method (for error messages).\n * @returns An object containing the account, checksummed address, and chainId.\n * @throws Error if the wallet has no active account.\n */\n private async resolveAccount(\n wallet: WalletClient,\n methodName: string\n ): Promise<{ account: NonNullable<WalletClient[\"account\"]>; address: string; chainId: number }> {\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 ${methodName}`\n );\n }\n const address = getAddress(resolvedAddress);\n const chainId = await wallet.getChainId();\n return { account, address, chainId };\n }\n\n /**\n * Signs a message and verifies it with retry logic.\n *\n * @param wallet - The Viem WalletClient instance.\n * @param account - The account to sign with.\n * @param message - The message to sign.\n * @param retry - Number of retry attempts for verification.\n * @param signal - Optional AbortSignal for request cancellation.\n * @param methodName - The name of the calling method (for error messages).\n * @returns A promise resolving to a Session object.\n */\n private async signAndVerifyWithRetry(\n wallet: WalletClient,\n account: NonNullable<WalletClient[\"account\"]>,\n message: string,\n retry: number,\n signal: AbortSignal | undefined,\n methodName: string\n ): Promise<Session> {\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(`${methodName} 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\n/**\n * Shared reference to sessionProvider so all modules use the same instance.\n */\ntype SessionProviderRef = { current: SessionProvider };\n\nexport abstract class ModuleBase {\n protected readonly ctx: MspClientContext;\n protected readonly sessionProviderRef: SessionProviderRef;\n\n constructor(ctx: MspClientContext, sessionProviderRef: SessionProviderRef) {\n this.ctx = ctx;\n this.sessionProviderRef = sessionProviderRef;\n }\n\n protected async withAuth(\n headers?: Record<string, string>\n ): Promise<Record<string, string> | undefined> {\n const session = await this.sessionProviderRef.current();\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", "sessionProviderRef", "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", "methodName", "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", "sessionProviderRef", "context", "AuthModule", "BucketsModule", "FilesModule", "InfoModule", "sessionProvider", "HttpClient"]
|
|
7
7
|
}
|
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;sessionProviderRef;constructor(e,t){this.ctx=e,this.sessionProviderRef=t}async withAuth(e){let n=(await this.sessionProviderRef.current())?.token;return n?e?{...e,Authorization:`Bearer ${n}`}:{Authorization:`Bearer ${n}`}:e}normalizePath(e){return e.replace(/^\/+|\/{2,}/g,(t,n)=>n===0?"":"/")}};var B=10,T=100,y=class extends l{getNonce(e,t,n,s,i){return this.ctx.http.post("/auth/nonce",{body:{address:e,chainId:t,domain:n,uri:s},headers:{"Content-Type":"application/json"},...i?{signal:i}:{}})}getMessage(e,t,n,s){return this.ctx.http.post("/auth/message",{body:{address:e,chainId:t,uri:n},headers:{"Content-Type":"application/json"},...s?{signal:s}:{}})}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,s=B,i){let{account:r,address:o,chainId:a}=await this.resolveAccount(e,"SIWE"),{message:p}=await this.getNonce(o,a,t,n,i);return this.signAndVerifyWithRetry(e,r,p,s,i,"SIWE")}async SIWX(e,t,n=B,s){let{account:i,address:r,chainId:o}=await this.resolveAccount(e,"SIWX"),{message:a}=await this.getMessage(r,o,t,s);return this.signAndVerifyWithRetry(e,i,a,n,s,"SIWX")}async resolveAccount(e,t){let n=e.account,s=typeof n=="string"?n:n?.address;if(!s||!n)throw new Error(`Wallet client has no active account; set wallet.account before calling ${t}`);let i=M(s),r=await e.getChainId();return{account:n,address:i,chainId:r}}async signAndVerifyWithRetry(e,t,n,s,i,r){let o=await e.signMessage({account:t,message:n}),a;for(let p=0;p<s;p++)try{return await this.verify(n,o,i)}catch(d){a=d,await this.delay(T)}throw a instanceof Error?a:new Error(`${r} 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 u,parseDate as H}from"@storagehub-sdk/core";function C(c){return c.type==="file"?{name:c.name,type:c.type,sizeBytes:c.sizeBytes,fileKey:u(c.fileKey),status:c.status,uploadedAt:H(c.uploadedAt)}:{name:c.name,type:c.type,children:(c.children??[]).map(C)}}var f=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(s=>({bucketId:u(s.bucketId),name:s.name,root:u(s.root),isPublic:s.isPublic,sizeBytes:s.sizeBytes,valuePropId:u(s.valuePropId),fileCount:s.fileCount}))}async getBucket(e,t){let n=await this.withAuth(),s=`/buckets/${encodeURIComponent(e)}`,i=await this.ctx.http.get(s,{...n?{headers:n}:{},...t?{signal:t}:{}});return{bucketId:u(i.bucketId),name:i.name,root:u(i.root),isPublic:i.isPublic,sizeBytes:i.sizeBytes,valuePropId:u(i.valuePropId),fileCount:i.fileCount}}async getFiles(e,t){let n=await this.withAuth(),s=`/buckets/${encodeURIComponent(e)}/files`,i=await this.ctx.http.get(s,{...n?{headers:n}:{},...t?.signal?{signal:t.signal}:{},...t?.path?{query:{path:this.normalizePath(t.path)}}:{}}),o=("files"in i?i.files:[i.tree]).map(C),a=o[0];return{bucketId:u(i.bucketId),files:o,...a?{tree:a}:{}}}};import{ensure0xPrefix as h,FileMetadata as E,FileTrie as z,hexToBytes as P,initWasm as F,parseDate as N}from"@storagehub-sdk/core";var g=class extends l{async getFileInfo(e,t,n){let s=await this.withAuth(),i=`/buckets/${encodeURIComponent(e)}/info/${encodeURIComponent(t)}`,r=await this.ctx.http.get(i,{...s?{headers:s}:{},...n?{signal:n}:{}});return{fileKey:h(r.fileKey),fingerprint:h(r.fingerprint),bucketId:h(r.bucketId),location:r.location,size:BigInt(r.size),isPublic:r.isPublic,uploadedAt:N(r.uploadedAt),status:r.status,blockHash:h(r.blockHash),...r.txHash?{txHash:h(r.txHash)}:{}}}async uploadFile(e,t,n,s,i,r){await F();let o=`/buckets/${encodeURIComponent(e)}/upload/${encodeURIComponent(t)}`,a=await this.withAuth(),p=await this.coerceToFormPart(n),d=p.size,w=await this.computeFileFingerprint(p),x=await this.formFileMetadata(s,e,i,w,BigInt(d)),S=await this.computeFileKey(x),A=P(t);if(S.length!==A.length||!S.every((U,W)=>U===A[W]))throw new Error(`Computed file key ${S.toString()} does not match provided file key ${A.toString()}`);let v=x.encode(),m=new FormData,k=new Blob([new Uint8Array(v)],{type:"application/octet-stream"});return m.append("file_metadata",k,"file_metadata"),m.append("file",p,"file"),await this.ctx.http.put(o,a?{body:m,headers:a}:{body:m})}async downloadFile(e,t){let n=`/download/${encodeURIComponent(e)}`,s={Accept:"*/*"};if(t?.range){let{start:r,end:o}=t.range,a=`bytes=${r}-${o??""}`;s.Range=a}let i=await this.withAuth(s);try{let r=await this.ctx.http.getRaw(n,{...i?{headers:i}:{},...t?.signal?{signal:t.signal}:{}});if(!r.body)throw new Error("Response body is null - unable to create stream");let o=r.headers.get("content-type"),a=r.headers.get("content-range"),p=r.headers.get("content-length"),d=p!==null?Number(p):void 0,w=typeof d=="number"&&Number.isFinite(d)?d:null;return{stream:r.body,status:r.status,contentType:o,contentRange:a,contentLength:w}}catch(r){if(this.isHttpError(r))return{stream:this.createEmptyStream(),status:r.status,contentType:null,contentRange:null,contentLength:null};throw r}}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=[],s=0;try{for(;;){let{done:o,value:a}=await t.read();if(o)break;a&&(n.push(a),s+=a.length)}}finally{t.releaseLock()}let i=new Uint8Array(s),r=0;for(let o of n)i.set(o,r),r+=o.length;return new Blob([i],{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()),s=1024,i=0;for(;i<n.length;){let r=Math.min(i+s,n.length),o=n.slice(i,r);t.push_chunk(o),i=r}return t.get_root()}async formFileMetadata(e,t,n,s,i){let r=P(e),o=P(t),a=new TextEncoder().encode(n);return await F(),new E(r,o,a,i,s)}async computeFileKey(e){return await F(),e.getFileKey()}};import{ensure0xPrefix as I}from"@storagehub-sdk/core";var b=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 R=class c extends l{config;context;auth;buckets;files;info;constructor(e,t,n){let s={config:e,http:t};super(s,n),this.config=e,this.context=s,this.auth=new y(this.context,n),this.buckets=new f(this.context,n),this.files=new g(this.context,n),this.info=new b(this.context,n)}static async connect(e,t=async()=>{}){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}}),s={current:t};return new c(e,n,s)}setSessionProvider(e){this.sessionProviderRef.current=e}};export{R 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 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": "AACA,OAAS,cAAAA,MAAkB,uBCA3B,OAAS,cAAAC,MAAqC,
|
|
6
|
-
"names": ["HttpClient", "getAddress", "ModuleBase", "ctx", "
|
|
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 sessionProviderRef: { current: SessionProvider }\n ) {\n const context: MspClientContext = { config, http };\n super(context, sessionProviderRef);\n this.config = config;\n this.context = context;\n this.auth = new AuthModule(this.context, sessionProviderRef);\n this.buckets = new BucketsModule(this.context, sessionProviderRef);\n this.files = new FilesModule(this.context, sessionProviderRef);\n this.info = new InfoModule(this.context, sessionProviderRef);\n }\n\n static async connect(\n config: HttpClientConfig,\n sessionProvider: SessionProvider = async () => undefined\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 // Create a shared reference object\n const sessionProviderRef = { current: sessionProvider };\n return new MspClient(config, http, sessionProviderRef);\n }\n\n /**\n * Updates the session provider for this client and all its modules.\n * This allows updating authentication after the client has been created.\n *\n * @param sessionProvider - The new session provider function.\n */\n setSessionProvider(sessionProvider: SessionProvider): void {\n this.sessionProviderRef.current = 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 * Request a message (challenge) for Sign-In with X (SIWX) using CAIP-122 standard.\n *\n * **Advanced use only:** Most users should use the `SIWX()` method instead, which handles the complete authentication flow automatically. This method is exposed only for custom authentication flows.\n *\n * This method follows the CAIP-122 standard for chain-agnostic authentication.\n * The message uses CAIP-10 format for addresses (e.g., `eip155:55931:0x...`).\n *\n * **Important:** The challenge message expires after a short time (typically 5 minutes).\n * You must call `verify()` with a valid signature before expiration.\n *\n * **Note:** According to CAIP-122, the domain is extracted from the URI automatically.\n * You do not need to provide the domain separately - it will be extracted from the URI.\n *\n * @param address - The blockchain address requesting authentication (checksummed format recommended).\n * @param chainId - The chain ID the user is connected to.\n * @param uri - The full URI of your dApp (e.g., \"https://datahaven.app\"). This should be the dApp URL, not the MSP API URL.\n * The domain will be automatically extracted from this URI per CAIP-122 specification.\n * @param signal - Optional AbortSignal for request cancellation.\n * @returns A promise resolving to the CAIP-122 challenge message to be signed.\n */\n public getMessage(\n address: string,\n chainId: number,\n uri: string,\n signal?: AbortSignal\n ): Promise<NonceResponse> {\n return this.ctx.http.post<NonceResponse>(\"/auth/message\", {\n body: {\n address,\n chainId,\n uri\n },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n }\n\n /**\n * Verify a Sign-In signature (works with both SIWE and SIWX/CAIP-122 messages).\n *\n * **Advanced use only:** Most users should use the `SIWE()` or `SIWX()` methods instead, which handle 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 challenge message received from `getNonce()` (SIWE) or `getMessage()` (CAIP-122).\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 const { account, address, chainId } = await this.resolveAccount(wallet, \"SIWE\");\n const { message } = await this.getNonce(address, chainId, domain, uri, signal);\n return this.signAndVerifyWithRetry(wallet, account, message, retry, signal, \"SIWE\");\n }\n\n /**\n * Complete Sign-In with X (SIWX) authentication flow using CAIP-122 standard and a `WalletClient`.\n *\n * This is the recommended method for CAIP-122 authentication. It handles the complete flow automatically:\n * derives the wallet address, fetches a CAIP-122 message, 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 * **CAIP-122:** Unlike SIWE, this method does not require a `domain` parameter. The domain is automatically extracted\n * from the `uri` parameter on the backend per CAIP-122 specification.\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 uri - The full URI of your dApp (e.g., \"https://datahaven.app\"). This should be the dApp URL, not the MSP API URL.\n * The domain will be automatically extracted from this URI per CAIP-122 specification.\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 SIWX(\n wallet: WalletClient,\n uri: string,\n retry = DEFAULT_SIWE_VERIFY_RETRY_ATTEMPS,\n signal?: AbortSignal\n ): Promise<Session> {\n const { account, address, chainId } = await this.resolveAccount(wallet, \"SIWX\");\n const { message } = await this.getMessage(address, chainId, uri, signal);\n return this.signAndVerifyWithRetry(wallet, account, message, retry, signal, \"SIWX\");\n }\n\n /**\n * Resolves and validates the account from a WalletClient.\n *\n * @param wallet - The Viem WalletClient instance.\n * @param methodName - The name of the calling method (for error messages).\n * @returns An object containing the account, checksummed address, and chainId.\n * @throws Error if the wallet has no active account.\n */\n private async resolveAccount(\n wallet: WalletClient,\n methodName: string\n ): Promise<{ account: NonNullable<WalletClient[\"account\"]>; address: string; chainId: number }> {\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 ${methodName}`\n );\n }\n const address = getAddress(resolvedAddress);\n const chainId = await wallet.getChainId();\n return { account, address, chainId };\n }\n\n /**\n * Signs a message and verifies it with retry logic.\n *\n * @param wallet - The Viem WalletClient instance.\n * @param account - The account to sign with.\n * @param message - The message to sign.\n * @param retry - Number of retry attempts for verification.\n * @param signal - Optional AbortSignal for request cancellation.\n * @param methodName - The name of the calling method (for error messages).\n * @returns A promise resolving to a Session object.\n */\n private async signAndVerifyWithRetry(\n wallet: WalletClient,\n account: NonNullable<WalletClient[\"account\"]>,\n message: string,\n retry: number,\n signal: AbortSignal | undefined,\n methodName: string\n ): Promise<Session> {\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(`${methodName} 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\n/**\n * Shared reference to sessionProvider so all modules use the same instance.\n */\ntype SessionProviderRef = { current: SessionProvider };\n\nexport abstract class ModuleBase {\n protected readonly ctx: MspClientContext;\n protected readonly sessionProviderRef: SessionProviderRef;\n\n constructor(ctx: MspClientContext, sessionProviderRef: SessionProviderRef) {\n this.ctx = ctx;\n this.sessionProviderRef = sessionProviderRef;\n }\n\n protected async withAuth(\n headers?: Record<string, string>\n ): Promise<Record<string, string> | undefined> {\n const session = await this.sessionProviderRef.current();\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": "AACA,OAAS,cAAAA,MAAkB,uBCA3B,OAAS,cAAAC,MAAqC,OCOvC,IAAeC,EAAf,KAA0B,CACZ,IACA,mBAEnB,YAAYC,EAAuBC,EAAwC,CACzE,KAAK,IAAMD,EACX,KAAK,mBAAqBC,CAC5B,CAEA,MAAgB,SACdC,EAC6C,CAE7C,IAAMC,GADU,MAAM,KAAK,mBAAmB,QAAQ,IAC/B,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,EDxCA,IAAMC,EAAoC,GACpCC,EAAiC,IAE1BC,EAAN,cAAyBC,CAAW,CAelC,SACLC,EACAC,EACAC,EACAC,EACAC,EACwB,CACxB,OAAO,KAAK,IAAI,KAAK,KAAoB,cAAe,CACtD,KAAM,CACJ,QAAAJ,EACA,QAAAC,EACA,OAAAC,EACA,IAAAC,CACF,EACA,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,GAAIC,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,CACH,CAuBO,WACLJ,EACAC,EACAE,EACAC,EACwB,CACxB,OAAO,KAAK,IAAI,KAAK,KAAoB,gBAAiB,CACxD,KAAM,CACJ,QAAAJ,EACA,QAAAC,EACA,IAAAE,CACF,EACA,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,GAAIC,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,CACH,CAeA,MAAa,OAAOC,EAAiBC,EAAmBF,EAAwC,CAO9F,OANgB,MAAM,KAAK,IAAI,KAAK,KAAc,eAAgB,CAChE,KAAM,CAAE,QAAAC,EAAS,UAAAC,CAAU,EAC3B,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,GAAIF,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,CAGH,CAyBA,MAAM,KACJG,EACAL,EACAC,EACAK,EAAQZ,EACRQ,EACkB,CAClB,GAAM,CAAE,QAAAK,EAAS,QAAAT,EAAS,QAAAC,CAAQ,EAAI,MAAM,KAAK,eAAeM,EAAQ,MAAM,EACxE,CAAE,QAAAF,CAAQ,EAAI,MAAM,KAAK,SAASL,EAASC,EAASC,EAAQC,EAAKC,CAAM,EAC7E,OAAO,KAAK,uBAAuBG,EAAQE,EAASJ,EAASG,EAAOJ,EAAQ,MAAM,CACpF,CA4BA,MAAM,KACJG,EACAJ,EACAK,EAAQZ,EACRQ,EACkB,CAClB,GAAM,CAAE,QAAAK,EAAS,QAAAT,EAAS,QAAAC,CAAQ,EAAI,MAAM,KAAK,eAAeM,EAAQ,MAAM,EACxE,CAAE,QAAAF,CAAQ,EAAI,MAAM,KAAK,WAAWL,EAASC,EAASE,EAAKC,CAAM,EACvE,OAAO,KAAK,uBAAuBG,EAAQE,EAASJ,EAASG,EAAOJ,EAAQ,MAAM,CACpF,CAUA,MAAc,eACZG,EACAG,EAC8F,CAC9F,IAAMD,EAAUF,EAAO,QACjBI,EAAkB,OAAOF,GAAY,SAAWA,EAAUA,GAAS,QACzE,GAAI,CAACE,GAAmB,CAACF,EACvB,MAAM,IAAI,MACR,0EAA0EC,CAAU,EACtF,EAEF,IAAMV,EAAUY,EAAWD,CAAe,EACpCV,EAAU,MAAMM,EAAO,WAAW,EACxC,MAAO,CAAE,QAAAE,EAAS,QAAAT,EAAS,QAAAC,CAAQ,CACrC,CAaA,MAAc,uBACZM,EACAE,EACAJ,EACAG,EACAJ,EACAM,EACkB,CAClB,IAAMJ,EAAY,MAAMC,EAAO,YAAY,CAAE,QAAAE,EAAS,QAAAJ,CAAQ,CAAC,EAG3DQ,EACJ,QAASC,EAAe,EAAGA,EAAeN,EAAOM,IAC/C,GAAI,CACF,OAAO,MAAM,KAAK,OAAOT,EAASC,EAAWF,CAAM,CACrD,OAASW,EAAK,CACZF,EAAYE,EACZ,MAAM,KAAK,MAAMlB,CAA8B,CACjD,CAEF,MAAMgB,aAAqB,MAAQA,EAAY,IAAI,MAAM,GAAGH,CAAU,sBAAsB,CAC9F,CAEA,MAAc,MAAMM,EAA2B,CAC7C,MAAM,IAAI,QAASC,GAAY,WAAWA,EAASD,CAAE,CAAC,CACxD,CAMA,MAAM,WAAWZ,EAAyC,CACxD,IAAMc,EAAU,MAAM,KAAK,SAAS,EACpC,OAAO,KAAK,IAAI,KAAK,IAAc,gBAAiB,CAClD,GAAIA,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC7B,GAAId,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,CACH,CACF,EErPA,OAAS,kBAAAe,EAAgB,aAAAC,MAAiB,uBAyB1C,SAASC,EAAYC,EAA8B,CACjD,OAAIA,EAAK,OAAS,OACT,CACL,KAAMA,EAAK,KACX,KAAMA,EAAK,KACX,UAAWA,EAAK,UAChB,QAASH,EAAeG,EAAK,OAAO,EACpC,OAAQA,EAAK,OACb,WAAYF,EAAUE,EAAK,UAAU,CACvC,EAEK,CACL,KAAMA,EAAK,KACX,KAAMA,EAAK,KACX,UAAWA,EAAK,UAAY,CAAC,GAAG,IAAID,CAAW,CACjD,CACF,CAEO,IAAME,EAAN,cAA4BC,CAAW,CAE5C,MAAM,YAAYC,EAAyC,CACzD,IAAMC,EAAU,MAAM,KAAK,SAAS,EAMpC,OALa,MAAM,KAAK,IAAI,KAAK,IAAc,WAAY,CACzD,GAAIA,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC7B,GAAID,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,GAEW,IAAKE,IAAoB,CACnC,SAAUR,EAAeQ,EAAO,QAAQ,EACxC,KAAMA,EAAO,KACb,KAAMR,EAAeQ,EAAO,IAAI,EAChC,SAAUA,EAAO,SACjB,UAAWA,EAAO,UAClB,YAAaR,EAAeQ,EAAO,WAAW,EAC9C,UAAWA,EAAO,SACpB,EAAE,CACJ,CAGA,MAAM,UAAUC,EAAkBH,EAAuC,CACvE,IAAMC,EAAU,MAAM,KAAK,SAAS,EAC9BG,EAAO,YAAY,mBAAmBD,CAAQ,CAAC,GAE/CE,EAAO,MAAM,KAAK,IAAI,KAAK,IAAYD,EAAM,CACjD,GAAIH,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC7B,GAAID,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,EAED,MAAO,CACL,SAAUN,EAAeW,EAAK,QAAQ,EACtC,KAAMA,EAAK,KACX,KAAMX,EAAeW,EAAK,IAAI,EAC9B,SAAUA,EAAK,SACf,UAAWA,EAAK,UAChB,YAAaX,EAAeW,EAAK,WAAW,EAC5C,UAAWA,EAAK,SAClB,CACF,CAGA,MAAM,SAASF,EAAkBG,EAAsD,CACrF,IAAML,EAAU,MAAM,KAAK,SAAS,EAC9BG,EAAO,YAAY,mBAAmBD,CAAQ,CAAC,SAE/CE,EAAO,MAAM,KAAK,IAAI,KAAK,IAA0BD,EAAM,CAC/D,GAAIH,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC7B,GAAIK,GAAS,OAAS,CAAE,OAAQA,EAAQ,MAAO,EAAI,CAAC,EACpD,GAAIA,GAAS,KAAO,CAAE,MAAO,CAAE,KAAM,KAAK,cAAcA,EAAQ,IAAI,CAAE,CAAE,EAAI,CAAC,CAC/E,CAAC,EAGKC,GADqC,UAAWF,EAAOA,EAAK,MAAQ,CAACA,EAAK,IAAI,GAChD,IAAIT,CAAW,EAC7CY,EAAOD,EAAM,CAAC,EACpB,MAAO,CACL,SAAUb,EAAeW,EAAK,QAAQ,EACtC,MAAAE,EACA,GAAIC,EAAO,CAAE,KAAAA,CAAK,EAAI,CAAC,CACzB,CACF,CACF,EC1GA,OACE,kBAAAC,EACA,gBAAAC,EACA,YAAAC,EACA,cAAAC,EACA,YAAAC,EACA,aAAAC,MACK,uBAWA,IAAMC,EAAN,cAA0BC,CAAW,CAE1C,MAAM,YACJC,EACAC,EACAC,EAC0B,CAC1B,IAAMC,EAAU,MAAM,KAAK,SAAS,EAC9BC,EAAO,YAAY,mBAAmBJ,CAAQ,CAAC,SAAS,mBAAmBC,CAAO,CAAC,GAEnFI,EAAO,MAAM,KAAK,IAAI,KAAK,IAW9BD,EAAM,CACP,GAAID,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC7B,GAAID,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,EAED,MAAO,CACL,QAASI,EAAeD,EAAK,OAAO,EACpC,YAAaC,EAAeD,EAAK,WAAW,EAC5C,SAAUC,EAAeD,EAAK,QAAQ,EACtC,SAAUA,EAAK,SACf,KAAM,OAAOA,EAAK,IAAI,EACtB,SAAUA,EAAK,SACf,WAAYE,EAAUF,EAAK,UAAU,EACrC,OAAQA,EAAK,OACb,UAAWC,EAAeD,EAAK,SAAS,EACxC,GAAIA,EAAK,OAAS,CAAE,OAAQC,EAAeD,EAAK,MAAM,CAAE,EAAI,CAAC,CAC/D,CACF,CAGA,MAAM,WACJL,EACAC,EACAO,EACAC,EACAC,EACAC,EACwB,CAGxB,MAAMC,EAAS,EAEf,IAAMC,EAAc,YAAY,mBAAmBb,CAAQ,CAAC,WAAW,mBAAmBC,CAAO,CAAC,GAC5Fa,EAAc,MAAM,KAAK,SAAS,EAGlCC,EAAW,MAAM,KAAK,iBAAiBP,CAAI,EAC3CQ,EAAWD,EAAS,KAGpBE,EAAc,MAAM,KAAK,uBAAuBF,CAAQ,EAGxDG,EAAW,MAAM,KAAK,iBAC1BT,EACAT,EACAU,EACAO,EACA,OAAOD,CAAQ,CACjB,EAGMG,EAAkB,MAAM,KAAK,eAAeD,CAAQ,EACpDE,EAAuBC,EAAWpB,CAAO,EAC/C,GACEkB,EAAgB,SAAWC,EAAqB,QAChD,CAACD,EAAgB,MAAM,CAACG,EAAMC,IAAUD,IAASF,EAAqBG,CAAK,CAAC,EAE5E,MAAM,IAAI,MACR,qBAAqBJ,EAAgB,SAAS,CAAC,qCAAqCC,EAAqB,SAAS,CAAC,EACrH,EAIF,IAAMI,EAAkBN,EAAS,OAAO,EAGlCO,EAAO,IAAI,SACXC,EAAmB,IAAI,KAAK,CAAC,IAAI,WAAWF,CAAe,CAAC,EAAG,CACnE,KAAM,0BACR,CAAC,EACD,OAAAC,EAAK,OAAO,gBAAiBC,EAAkB,eAAe,EAC9DD,EAAK,OAAO,OAAQV,EAAU,MAAM,EAExB,MAAM,KAAK,IAAI,KAAK,IAC9BF,EACAC,EACI,CAAE,KAAMW,EAA6B,QAASX,CAAY,EAC1D,CAAE,KAAMW,CAA4B,CAC1C,CAEF,CAGA,MAAM,aAAaxB,EAAiB0B,EAAoD,CACtF,IAAMvB,EAAO,aAAa,mBAAmBH,CAAO,CAAC,GAC/C2B,EAAsC,CAAE,OAAQ,KAAM,EAC5D,GAAID,GAAS,MAAO,CAClB,GAAM,CAAE,MAAAE,EAAO,IAAAC,CAAI,EAAIH,EAAQ,MACzBI,EAAa,SAASF,CAAK,IAAIC,GAAO,EAAE,GAC9CF,EAAY,MAAQG,CACtB,CAEA,IAAM5B,EAAU,MAAM,KAAK,SAASyB,CAAW,EAE/C,GAAI,CACF,IAAMI,EAAM,MAAM,KAAK,IAAI,KAAK,OAAO5B,EAAM,CAC3C,GAAID,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC7B,GAAIwB,GAAS,OAAS,CAAE,OAAQA,EAAQ,MAAO,EAAI,CAAC,CACtD,CAAC,EAED,GAAI,CAACK,EAAI,KACP,MAAM,IAAI,MAAM,iDAAiD,EAGnE,IAAMC,EAAcD,EAAI,QAAQ,IAAI,cAAc,EAC5CE,EAAeF,EAAI,QAAQ,IAAI,eAAe,EAC9CG,EAAsBH,EAAI,QAAQ,IAAI,gBAAgB,EACtDI,EAAeD,IAAwB,KAAO,OAAOA,CAAmB,EAAI,OAC5EE,EACJ,OAAOD,GAAiB,UAAY,OAAO,SAASA,CAAY,EAAIA,EAAe,KAErF,MAAO,CACL,OAAQJ,EAAI,KACZ,OAAQA,EAAI,OACZ,YAAAC,EACA,aAAAC,EACA,cAAAG,CACF,CACF,OAASC,EAAO,CAEd,GAAI,KAAK,YAAYA,CAAK,EACxB,MAAO,CACL,OAAQ,KAAK,kBAAkB,EAC/B,OAAQA,EAAM,OACd,YAAa,KACb,aAAc,KACd,cAAe,IACjB,EAGF,MAAMA,CACR,CACF,CAGQ,YAAYA,EAA6C,CAC/D,OACEA,IAAU,MACV,OAAOA,GAAU,UACjB,WAAYA,GACZ,OAAOA,EAAM,QAAW,QAE5B,CAEQ,mBAAgD,CACtD,OAAO,IAAI,eAA2B,CACpC,MAAMC,EAAY,CAChBA,EAAW,MAAM,CACnB,CACF,CAAC,CACH,CAEA,MAAc,iBACZ/B,EACe,CACf,GAAI,OAAO,KAAS,KAAeA,aAAgB,KAAM,OAAOA,EAChE,GAAIA,aAAgB,WAAY,OAAO,IAAI,KAAK,CAACA,EAAK,MAAqB,CAAC,EAC5E,GAAI,OAAO,YAAgB,KAAeA,aAAgB,YAAa,OAAO,IAAI,KAAK,CAACA,CAAI,CAAC,EAG7F,GAAIA,aAAgB,eAAgB,CAClC,IAAMgC,EAAShC,EAAK,UAAU,EACxBiC,EAAuB,CAAC,EAC1BC,EAAc,EAElB,GAAI,CACF,OAAa,CACX,GAAM,CAAE,KAAAC,EAAM,MAAAC,CAAM,EAAI,MAAMJ,EAAO,KAAK,EAC1C,GAAIG,EAAM,MACNC,IACFH,EAAO,KAAKG,CAAK,EACjBF,GAAeE,EAAM,OAEzB,CACF,QAAE,CACAJ,EAAO,YAAY,CACrB,CAGA,IAAMK,EAAW,IAAI,WAAWH,CAAW,EACvCI,EAAS,EACb,QAAWC,KAASN,EAClBI,EAAS,IAAIE,EAAOD,CAAM,EAC1BA,GAAUC,EAAM,OAGlB,OAAO,IAAI,KAAK,CAACF,CAAQ,EAAG,CAAE,KAAM,0BAA2B,CAAC,CAClE,CAEA,OAAO,IAAI,KAAK,CAACrC,CAAgB,EAAG,CAAE,KAAM,0BAA2B,CAAC,CAC1E,CAEA,MAAc,uBAAuBO,EAAqC,CACxE,IAAMiC,EAAO,IAAIC,EACXC,EAAY,IAAI,WAAW,MAAMnC,EAAS,YAAY,CAAC,EAGvDoC,EAAa,KACfL,EAAS,EAEb,KAAOA,EAASI,EAAU,QAAQ,CAChC,IAAMpB,EAAM,KAAK,IAAIgB,EAASK,EAAYD,EAAU,MAAM,EACpDH,EAAQG,EAAU,MAAMJ,EAAQhB,CAAG,EACzCkB,EAAK,WAAWD,CAAK,EACrBD,EAAShB,CACX,CAEA,OAAOkB,EAAK,SAAS,CACvB,CAEA,MAAc,iBACZvC,EACAT,EACAU,EACAO,EACAmC,EACuB,CACvB,IAAMC,EAAahC,EAAWZ,CAAK,EAC7B6C,EAAgBjC,EAAWrB,CAAQ,EACnCuD,EAAgB,IAAI,YAAY,EAAE,OAAO7C,CAAQ,EACvD,aAAME,EAAS,EACR,IAAI4C,EAAaH,EAAYC,EAAeC,EAAeH,EAAMnC,CAAW,CACrF,CAEA,MAAc,eAAewC,EAAiD,CAC5E,aAAM7C,EAAS,EACR6C,EAAa,WAAW,CACjC,CACF,ECpQA,OAAS,kBAAAC,MAAsB,uBAExB,IAAMC,EAAN,cAAyBC,CAAW,CACzC,UAAUC,EAA6C,CACrD,OAAO,KAAK,IAAI,KAAK,IAAkB,UAAW,CAChD,GAAIA,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,CACH,CAGA,MAAM,QAAQA,EAA6C,CACzD,IAAMC,EAAO,MAAM,KAAK,IAAI,KAAK,IAU9B,QAAS,CACV,GAAID,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,EAED,MAAO,CACL,OAAQC,EAAK,OACb,QAASA,EAAK,QACd,MAAOJ,EAAeI,EAAK,KAAK,EAChC,eAAgBA,EAAK,eACrB,aAAcJ,EAAeI,EAAK,YAAY,EAC9C,eAAgBJ,EAAeI,EAAK,cAAc,EAClD,OAAQA,EAAK,OACb,YAAaA,EAAK,YAClB,OAAQA,EAAK,MACf,CACF,CAGA,SAASD,EAA8C,CACrD,OAAO,KAAK,IAAI,KAAK,IAAmB,SAAU,CAChD,GAAIA,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,CACH,CAGA,qBAAqBA,EAA4C,CAC/D,OAAO,KAAK,IAAI,KAAK,IAAiB,eAAgB,CACpD,GAAIA,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,CACH,CAGA,MAAM,kBAAkBA,EAAuD,CAC7E,IAAME,EAAU,MAAM,KAAK,SAAS,EACpC,OAAO,KAAK,IAAI,KAAK,IAA4B,mBAAoB,CACnE,GAAIA,EAAU,CAAE,QAAAA,CAAQ,EAAI,CAAC,EAC7B,GAAIF,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,CAC7B,CAAC,CACH,CACF,EL1DO,IAAMG,EAAN,MAAMC,UAAkBC,CAAW,CACxB,OACC,QACD,KACA,QACA,MACA,KAER,YACNC,EACAC,EACAC,EACA,CACA,IAAMC,EAA4B,CAAE,OAAAH,EAAQ,KAAAC,CAAK,EACjD,MAAME,EAASD,CAAkB,EACjC,KAAK,OAASF,EACd,KAAK,QAAUG,EACf,KAAK,KAAO,IAAIC,EAAW,KAAK,QAASF,CAAkB,EAC3D,KAAK,QAAU,IAAIG,EAAc,KAAK,QAASH,CAAkB,EACjE,KAAK,MAAQ,IAAII,EAAY,KAAK,QAASJ,CAAkB,EAC7D,KAAK,KAAO,IAAIK,EAAW,KAAK,QAASL,CAAkB,CAC7D,CAEA,aAAa,QACXF,EACAQ,EAAmC,SAAS,GACxB,CACpB,GAAI,CAACR,GAAQ,QAAS,MAAM,IAAI,MAAM,wCAAwC,EAE9E,IAAMC,EAAO,IAAIQ,EAAW,CAC1B,QAAST,EAAO,QAChB,GAAIA,EAAO,YAAc,QAAa,CAAE,UAAWA,EAAO,SAAU,EACpE,GAAIA,EAAO,iBAAmB,QAAa,CACzC,eAAgBA,EAAO,cACzB,EACA,GAAIA,EAAO,YAAc,QAAa,CAAE,UAAWA,EAAO,SAAU,CACtE,CAAC,EAGKE,EAAqB,CAAE,QAASM,CAAgB,EACtD,OAAO,IAAIV,EAAUE,EAAQC,EAAMC,CAAkB,CACvD,CAQA,mBAAmBM,EAAwC,CACzD,KAAK,mBAAmB,QAAUA,CACpC,CACF",
|
|
6
|
+
"names": ["HttpClient", "getAddress", "ModuleBase", "ctx", "sessionProviderRef", "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", "methodName", "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", "sessionProviderRef", "context", "AuthModule", "BucketsModule", "FilesModule", "InfoModule", "sessionProvider", "HttpClient"]
|
|
7
7
|
}
|
package/dist/modules/auth.d.ts
CHANGED
|
@@ -18,14 +18,36 @@ export declare class AuthModule extends ModuleBase {
|
|
|
18
18
|
*/
|
|
19
19
|
getNonce(address: string, chainId: number, domain: string, uri: string, signal?: AbortSignal): Promise<NonceResponse>;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* Request a message (challenge) for Sign-In with X (SIWX) using CAIP-122 standard.
|
|
22
22
|
*
|
|
23
|
-
* **Advanced use only:** Most users should use the `
|
|
23
|
+
* **Advanced use only:** Most users should use the `SIWX()` method instead, which handles the complete authentication flow automatically. This method is exposed only for custom authentication flows.
|
|
24
|
+
*
|
|
25
|
+
* This method follows the CAIP-122 standard for chain-agnostic authentication.
|
|
26
|
+
* The message uses CAIP-10 format for addresses (e.g., `eip155:55931:0x...`).
|
|
27
|
+
*
|
|
28
|
+
* **Important:** The challenge message expires after a short time (typically 5 minutes).
|
|
29
|
+
* You must call `verify()` with a valid signature before expiration.
|
|
30
|
+
*
|
|
31
|
+
* **Note:** According to CAIP-122, the domain is extracted from the URI automatically.
|
|
32
|
+
* You do not need to provide the domain separately - it will be extracted from the URI.
|
|
33
|
+
*
|
|
34
|
+
* @param address - The blockchain address requesting authentication (checksummed format recommended).
|
|
35
|
+
* @param chainId - The chain ID the user is connected to.
|
|
36
|
+
* @param uri - The full URI of your dApp (e.g., "https://datahaven.app"). This should be the dApp URL, not the MSP API URL.
|
|
37
|
+
* The domain will be automatically extracted from this URI per CAIP-122 specification.
|
|
38
|
+
* @param signal - Optional AbortSignal for request cancellation.
|
|
39
|
+
* @returns A promise resolving to the CAIP-122 challenge message to be signed.
|
|
40
|
+
*/
|
|
41
|
+
getMessage(address: string, chainId: number, uri: string, signal?: AbortSignal): Promise<NonceResponse>;
|
|
42
|
+
/**
|
|
43
|
+
* Verify a Sign-In signature (works with both SIWE and SIWX/CAIP-122 messages).
|
|
44
|
+
*
|
|
45
|
+
* **Advanced use only:** Most users should use the `SIWE()` or `SIWX()` methods instead, which handle the complete authentication flow automatically. This method is exposed only for custom authentication flows.
|
|
24
46
|
*
|
|
25
47
|
* **Important:** You must store the returned Session object and provide it via the `sessionProvider` function passed to `MspClient.connect()`.
|
|
26
48
|
* The session is not automatically persisted - you are responsible for managing session storage and ensuring your `sessionProvider` returns it for subsequent authenticated requests.
|
|
27
49
|
*
|
|
28
|
-
* @param message - The
|
|
50
|
+
* @param message - The challenge message received from `getNonce()` (SIWE) or `getMessage()` (CAIP-122).
|
|
29
51
|
* @param signature - The signature of the message signed by the user's wallet.
|
|
30
52
|
* @param signal - Optional AbortSignal for request cancellation.
|
|
31
53
|
* @returns A promise resolving to a Session object that you must store and provide via your sessionProvider.
|
|
@@ -55,6 +77,54 @@ export declare class AuthModule extends ModuleBase {
|
|
|
55
77
|
* @returns A promise resolving to a Session object that you must store and provide via your sessionProvider.
|
|
56
78
|
*/
|
|
57
79
|
SIWE(wallet: WalletClient, domain: string, uri: string, retry?: number, signal?: AbortSignal): Promise<Session>;
|
|
80
|
+
/**
|
|
81
|
+
* Complete Sign-In with X (SIWX) authentication flow using CAIP-122 standard and a `WalletClient`.
|
|
82
|
+
*
|
|
83
|
+
* This is the recommended method for CAIP-122 authentication. It handles the complete flow automatically:
|
|
84
|
+
* derives the wallet address, fetches a CAIP-122 message, prompts the user to sign the message, verifies the signature,
|
|
85
|
+
* and returns a session token.
|
|
86
|
+
*
|
|
87
|
+
* **Important:** You must store the returned Session object and provide it via the `sessionProvider` function
|
|
88
|
+
* passed to `MspClient.connect()`. The session is not automatically persisted - you are responsible for managing
|
|
89
|
+
* session storage and ensuring your `sessionProvider` returns it for subsequent authenticated requests.
|
|
90
|
+
*
|
|
91
|
+
* **Note:** This method includes automatic retry logic for verification requests (default: 10 attempts with 100ms backoff).
|
|
92
|
+
* The retry behavior can be customized via the `retry` parameter.
|
|
93
|
+
*
|
|
94
|
+
* **CAIP-122:** Unlike SIWE, this method does not require a `domain` parameter. The domain is automatically extracted
|
|
95
|
+
* from the `uri` parameter on the backend per CAIP-122 specification.
|
|
96
|
+
*
|
|
97
|
+
* @param wallet - The Viem `WalletClient` instance. Must have an active account set (`wallet.account`).
|
|
98
|
+
* - Browser wallets (e.g., MetaMask) automatically surface the user-selected address.
|
|
99
|
+
* - Viem/local wallets must set `wallet.account` explicitly before calling.
|
|
100
|
+
* @param uri - The full URI of your dApp (e.g., "https://datahaven.app"). This should be the dApp URL, not the MSP API URL.
|
|
101
|
+
* The domain will be automatically extracted from this URI per CAIP-122 specification.
|
|
102
|
+
* @param retry - Number of retry attempts for verification requests (default: 10).
|
|
103
|
+
* @param signal - Optional AbortSignal for request cancellation.
|
|
104
|
+
* @returns A promise resolving to a Session object that you must store and provide via your sessionProvider.
|
|
105
|
+
*/
|
|
106
|
+
SIWX(wallet: WalletClient, uri: string, retry?: number, signal?: AbortSignal): Promise<Session>;
|
|
107
|
+
/**
|
|
108
|
+
* Resolves and validates the account from a WalletClient.
|
|
109
|
+
*
|
|
110
|
+
* @param wallet - The Viem WalletClient instance.
|
|
111
|
+
* @param methodName - The name of the calling method (for error messages).
|
|
112
|
+
* @returns An object containing the account, checksummed address, and chainId.
|
|
113
|
+
* @throws Error if the wallet has no active account.
|
|
114
|
+
*/
|
|
115
|
+
private resolveAccount;
|
|
116
|
+
/**
|
|
117
|
+
* Signs a message and verifies it with retry logic.
|
|
118
|
+
*
|
|
119
|
+
* @param wallet - The Viem WalletClient instance.
|
|
120
|
+
* @param account - The account to sign with.
|
|
121
|
+
* @param message - The message to sign.
|
|
122
|
+
* @param retry - Number of retry attempts for verification.
|
|
123
|
+
* @param signal - Optional AbortSignal for request cancellation.
|
|
124
|
+
* @param methodName - The name of the calling method (for error messages).
|
|
125
|
+
* @returns A promise resolving to a Session object.
|
|
126
|
+
*/
|
|
127
|
+
private signAndVerifyWithRetry;
|
|
58
128
|
private delay;
|
|
59
129
|
/**
|
|
60
130
|
* Fetch authenticated user's profile.
|
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.5-dev.1",
|
|
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",
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
],
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@storagehub-sdk/core": ">=0.0.5",
|
|
27
|
-
"viem": ">=2.
|
|
27
|
+
"viem": ">=2.41.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"viem": ">=2.
|
|
31
|
-
"@storagehub-sdk/core": "0.3.
|
|
30
|
+
"viem": ">=2.41.2",
|
|
31
|
+
"@storagehub-sdk/core": "0.3.5-dev.1"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=22"
|