@storagehub-sdk/msp-client 0.1.0 → 0.2.0
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/README.md +38 -46
- package/dist/MspClient.d.ts +12 -64
- package/dist/base.d.ts +19 -0
- package/dist/context.d.ts +7 -0
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.node.js +1 -1
- package/dist/index.node.js.map +4 -4
- package/dist/modules/auth.d.ts +30 -0
- package/dist/modules/buckets.d.ts +10 -0
- package/dist/modules/files.d.ts +15 -0
- package/dist/modules/info.d.ts +13 -0
- package/dist/types.d.ts +35 -10
- package/package.json +9 -14
- package/dist/tests/MspClient.spec.d.ts +0 -1
package/README.md
CHANGED
|
@@ -60,21 +60,16 @@ const client = await MspClient.connect({
|
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
// 2. Check service health
|
|
63
|
-
const health = await client.getHealth();
|
|
63
|
+
const health = await client.info.getHealth();
|
|
64
64
|
console.log('MSP service health:', health);
|
|
65
65
|
|
|
66
66
|
// 3. Authenticate with wallet (SIWE-style)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// After signing with your wallet (e.g., MetaMask, WalletConnect, etc.)
|
|
75
|
-
const signature = '0xYourWalletSignature...'; // Replace with actual signature
|
|
76
|
-
const verified = await client.verify(message, signature);
|
|
77
|
-
client.setToken(verified.token); // Set auth token for subsequent requests
|
|
67
|
+
// Example with viem's WalletClient
|
|
68
|
+
import { createWalletClient, http } from 'viem';
|
|
69
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
70
|
+
const account = privateKeyToAccount('0x<your_dev_private_key>');
|
|
71
|
+
const wallet = createWalletClient({ account, transport: http('http://127.0.0.1:8545') });
|
|
72
|
+
await client.auth.SIWE(wallet);
|
|
78
73
|
|
|
79
74
|
// 4. Upload a file
|
|
80
75
|
const bucketId = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; // StorageHub bucket identifier
|
|
@@ -83,11 +78,11 @@ const filePath = './myfile.txt';
|
|
|
83
78
|
const owner = walletAddress; // File owner
|
|
84
79
|
const location = 'myfile.txt'; // File location/path within the bucket
|
|
85
80
|
|
|
86
|
-
const receipt = await client.uploadFile(bucketId, fileKey, createReadStream(filePath), owner, location);
|
|
81
|
+
const receipt = await client.files.uploadFile(bucketId, fileKey, createReadStream(filePath), owner, location);
|
|
87
82
|
console.log('File uploaded successfully:', receipt);
|
|
88
83
|
|
|
89
84
|
// 5. Download the file
|
|
90
|
-
const download = await client.
|
|
85
|
+
const download = await client.files.downloadFile(fileKey);
|
|
91
86
|
const outputPath = './downloaded-file.txt';
|
|
92
87
|
|
|
93
88
|
// Stream the download to a file
|
|
@@ -103,20 +98,20 @@ console.log('File downloaded successfully to:', outputPath);
|
|
|
103
98
|
console.log('Download status:', download.status);
|
|
104
99
|
|
|
105
100
|
// 6. List the buckets of the currently authenticated user
|
|
106
|
-
const buckets = await client.listBuckets();
|
|
101
|
+
const buckets = await client.buckets.listBuckets();
|
|
107
102
|
console.log('Buckets:', buckets);
|
|
108
103
|
|
|
109
104
|
// 7. Get the metadata of a specific bucket
|
|
110
|
-
const bucket = await client.getBucket(bucketId);
|
|
105
|
+
const bucket = await client.buckets.getBucket(bucketId);
|
|
111
106
|
console.log('Bucket:', bucket);
|
|
112
107
|
|
|
113
108
|
// 8. Get the files of the root folder of a specific bucket
|
|
114
|
-
const files = await client.getFiles(bucketId);
|
|
109
|
+
const files = await client.buckets.getFiles(bucketId);
|
|
115
110
|
console.log('Root files:', files);
|
|
116
111
|
|
|
117
112
|
// 9. Get the files of a specific folder of a specific bucket
|
|
118
|
-
const
|
|
119
|
-
console.log('Folder files:',
|
|
113
|
+
const folderFiles = await client.buckets.getFiles(bucketId, { path: '/path/to/folder' });
|
|
114
|
+
console.log('Folder files:', folderFiles);
|
|
120
115
|
```
|
|
121
116
|
|
|
122
117
|
## API Reference
|
|
@@ -128,30 +123,27 @@ console.log('Folder files:', files);
|
|
|
128
123
|
- `config.defaultHeaders?: Record<string, string>` - Default HTTP headers
|
|
129
124
|
- `config.fetchImpl?: typeof fetch` - Custom fetch implementation
|
|
130
125
|
|
|
131
|
-
###
|
|
132
|
-
- **`
|
|
133
|
-
-
|
|
134
|
-
- `
|
|
135
|
-
- `
|
|
136
|
-
- **`
|
|
137
|
-
- `
|
|
138
|
-
- `
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
- `
|
|
142
|
-
|
|
143
|
-
- `
|
|
144
|
-
- `
|
|
145
|
-
- `
|
|
146
|
-
- **`
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
|
|
155
|
-
- `options?: { path?: string, signal?: AbortSignal }` - Optional parameters
|
|
156
|
-
- `path?: string` - Path to the folder to get the files from
|
|
157
|
-
- `signal?: AbortSignal` - Abort signal to cancel the request
|
|
126
|
+
### Modules (instance properties)
|
|
127
|
+
- **`auth`**: SIWE auth and session helpers
|
|
128
|
+
- `SIWE(wallet, signal?)` – runs full SIWE flow and stores session
|
|
129
|
+
- `getProfile(signal?)` – returns the authenticated user's profile
|
|
130
|
+
- `getAuthStatus()` – returns NotAuthenticated | TokenExpired | Authenticated
|
|
131
|
+
- **`info`**: MSP info and stats
|
|
132
|
+
- `getHealth(signal?)` – returns service health and status
|
|
133
|
+
- `getInfo(signal?)` – returns general MSP info (id, version, owner, endpoints)
|
|
134
|
+
- `getStats(signal?)` – returns capacity and usage stats
|
|
135
|
+
- `getValuePropositions(signal?)` – returns available value props/pricing
|
|
136
|
+
- `getPaymentStreams(signal?)` – returns the authenticated user's payment streams
|
|
137
|
+
- **`buckets`**: Buckets and file listings
|
|
138
|
+
- `listBuckets(signal?)` – returns all buckets for the current authenticated user
|
|
139
|
+
- `getBucket(bucketId, signal?)` – returns metadata for a specific bucket
|
|
140
|
+
- `getFiles(bucketId, { path?, signal? })` – returns the file tree at root or at a subpath
|
|
141
|
+
- **`files`**: File metadata, upload and download
|
|
142
|
+
- `getFileInfo(bucketId, fileKey, signal?)` – returns metadata for a specific file
|
|
143
|
+
- `uploadFile(...)` – uploads a file to the MSP
|
|
144
|
+
- `downloadFile(fileKey, options?)` – downloads a file by key (supports range)
|
|
145
|
+
|
|
146
|
+
### Utilities available via `files`
|
|
147
|
+
- `hexToBytes(hex)`
|
|
148
|
+
- `formFileMetadata(owner, bucketId, location, fingerprint, size)`
|
|
149
|
+
- `computeFileKey(metadata)`
|
package/dist/MspClient.d.ts
CHANGED
|
@@ -1,68 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import type { HttpClientConfig } from "@storagehub-sdk/core";
|
|
2
|
+
import { AuthModule } from "./modules/auth.js";
|
|
3
|
+
import { BucketsModule } from "./modules/buckets.js";
|
|
4
|
+
import { ModuleBase } from "./base.js";
|
|
5
|
+
import { FilesModule } from "./modules/files.js";
|
|
6
|
+
import { InfoModule } from "./modules/info.js";
|
|
7
|
+
export declare class MspClient extends ModuleBase {
|
|
5
8
|
readonly config: HttpClientConfig;
|
|
6
|
-
private readonly
|
|
7
|
-
|
|
9
|
+
private readonly context;
|
|
10
|
+
readonly auth: AuthModule;
|
|
11
|
+
readonly buckets: BucketsModule;
|
|
12
|
+
readonly files: FilesModule;
|
|
13
|
+
readonly info: InfoModule;
|
|
8
14
|
private constructor();
|
|
9
15
|
static connect(config: HttpClientConfig): Promise<MspClient>;
|
|
10
|
-
getHealth(options?: {
|
|
11
|
-
signal?: AbortSignal;
|
|
12
|
-
}): Promise<HealthStatus>;
|
|
13
|
-
/** Get general MSP information */
|
|
14
|
-
getInfo(options?: {
|
|
15
|
-
signal?: AbortSignal;
|
|
16
|
-
}): Promise<InfoResponse>;
|
|
17
|
-
/** Get MSP statistics */
|
|
18
|
-
getStats(options?: {
|
|
19
|
-
signal?: AbortSignal;
|
|
20
|
-
}): Promise<StatsResponse>;
|
|
21
|
-
/** Get available value propositions */
|
|
22
|
-
getValuePropositions(options?: {
|
|
23
|
-
signal?: AbortSignal;
|
|
24
|
-
}): Promise<ValueProp[]>;
|
|
25
|
-
/** Request a SIWE-style nonce message for the given address and chainId */
|
|
26
|
-
getNonce(address: string, chainId: number, options?: {
|
|
27
|
-
signal?: AbortSignal;
|
|
28
|
-
}): Promise<NonceResponse>;
|
|
29
|
-
/** Verify signed message and receive JWT token */
|
|
30
|
-
verify(message: string, signature: string, options?: {
|
|
31
|
-
signal?: AbortSignal;
|
|
32
|
-
}): Promise<VerifyResponse>;
|
|
33
|
-
/** Store token to be sent on subsequent protected requests */
|
|
34
|
-
setToken(token: string): void;
|
|
35
|
-
/** Merge Authorization header when token is present */
|
|
36
|
-
private withAuth;
|
|
37
|
-
/** List all buckets for the current authenticateduser */
|
|
38
|
-
listBuckets(options?: {
|
|
39
|
-
signal?: AbortSignal;
|
|
40
|
-
}): Promise<Bucket[]>;
|
|
41
|
-
/** Get a specific bucket's metadata by its bucket ID */
|
|
42
|
-
getBucket(bucketId: string, options?: {
|
|
43
|
-
signal?: AbortSignal;
|
|
44
|
-
}): Promise<Bucket>;
|
|
45
|
-
/** Gets the list of files and folders under the specified path for a bucket. If no path is provided, it returns the files and folders found at root. */
|
|
46
|
-
getFiles(bucketId: string, options?: GetFilesOptions): Promise<FileListResponse>;
|
|
47
|
-
/** Get metadata for a file in a bucket by fileKey */
|
|
48
|
-
getFileInfo(bucketId: string, fileKey: string, options?: {
|
|
49
|
-
signal?: AbortSignal;
|
|
50
|
-
}): Promise<FileInfo>;
|
|
51
|
-
/**
|
|
52
|
-
* Upload a file to a bucket with a specific key.
|
|
53
|
-
*
|
|
54
|
-
* Always uses multipart/form-data upload with both file data and encoded FileMetadata.
|
|
55
|
-
* The file data is loaded into memory to create the multipart request.
|
|
56
|
-
*
|
|
57
|
-
*/
|
|
58
|
-
uploadFile(bucketId: string, fileKey: string, file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown, owner: string, location: string, _options?: UploadOptions): Promise<UploadReceipt>;
|
|
59
|
-
private coerceToFormPart;
|
|
60
|
-
private computeFileFingerprint;
|
|
61
|
-
formFileMetadata(owner: string, bucketId: string, location: string, fingerprint: Uint8Array, size: bigint): Promise<FileMetadata>;
|
|
62
|
-
hexToBytes(hex: string): Uint8Array;
|
|
63
|
-
computeFileKey(fileMetadata: FileMetadata): Promise<Uint8Array>;
|
|
64
|
-
/** Download a file by key. */
|
|
65
|
-
downloadByKey(fileKey: string, options?: DownloadOptions): Promise<DownloadResult>;
|
|
66
|
-
/** Download a file by its location path under a bucket. */
|
|
67
|
-
downloadByLocation(bucketId: string, filePath: string, options?: DownloadOptions): Promise<DownloadResult>;
|
|
68
16
|
}
|
package/dist/base.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MspClientContext } from "./context.js";
|
|
2
|
+
export declare abstract class ModuleBase {
|
|
3
|
+
protected readonly ctx: MspClientContext;
|
|
4
|
+
constructor(ctx: MspClientContext);
|
|
5
|
+
protected withAuth(headers?: Record<string, string>): Record<string, string> | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Normalize a user-provided path for HTTP query usage.
|
|
8
|
+
* - Removes all leading '/' characters to avoid double slashes in URLs.
|
|
9
|
+
* - Collapses any repeated slashes in the middle or at the end to a single '/'.
|
|
10
|
+
* Examples:
|
|
11
|
+
* "/foo/bar" -> "foo/bar"
|
|
12
|
+
* "///docs" -> "docs"
|
|
13
|
+
* "foo//bar" -> "foo/bar"
|
|
14
|
+
* "///a//b///" -> "a/b/"
|
|
15
|
+
* "foo/bar" -> "foo/bar" (unchanged)
|
|
16
|
+
* "/" -> ""
|
|
17
|
+
*/
|
|
18
|
+
protected normalizePath(path: string): string;
|
|
19
|
+
}
|
package/dist/index.browser.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{HttpClient as F}from"@storagehub-sdk/core";import{getAddress as C}from"viem";var c=class{ctx;constructor(t){this.ctx=t}withAuth(t){let e=this.ctx.session?.token;return e?{...t??{},Authorization:`Bearer ${e}`}:t}normalizePath(t){return t.replace(/^\/+|\/{2,}/g,(e,n)=>n===0?"":"/")}};var f=class extends c{getNonce(t,e,n){return this.ctx.http.post("/auth/nonce",{body:{address:t,chainId:e},headers:{"Content-Type":"application/json"},...n?{signal:n}:{}})}async verify(t,e,n){let r=await this.ctx.http.post("/auth/verify",{body:{message:t,signature:e},headers:{"Content-Type":"application/json"},...n?{signal:n}:{}});return this.ctx.session=r,r}async SIWE(t,e){let n=t.account,r=typeof n=="string"?n:n?.address;if(!r||!n)throw new Error("Wallet client has no active account; set wallet.account before calling SIWE");let o=C(r),s=await t.getChainId(),{message:i}=await this.getNonce(o,s,e),a=await t.signMessage({account:n,message:i});this.ctx.session=await this.verify(i,a,e)}getProfile(t){let e=this.withAuth();return this.ctx.http.get("/auth/profile",{...e?{headers:e}:{},...t?{signal:t}:{}})}async getAuthStatus(){return this.ctx.session?.token?await this.getProfile().catch(e=>e?.response?.status===401?null:Promise.reject(e))?{status:"Authenticated"}:{status:"TokenExpired"}:{status:"NotAuthenticated"}}};var y=class extends c{listBuckets(t){let e=this.withAuth();return this.ctx.http.get("/buckets",{...e?{headers:e}:{},...t?{signal:t}:{}})}getBucket(t,e){let n=this.withAuth(),r=`/buckets/${encodeURIComponent(t)}`;return this.ctx.http.get(r,{...n?{headers:n}:{},...e?{signal:e}:{}})}getFiles(t,e){let n=this.withAuth(),r=`/buckets/${encodeURIComponent(t)}/files`;return this.ctx.http.get(r,{...n?{headers:n}:{},...e?.signal?{signal:e.signal}:{},...e?.path?{query:{path:this.normalizePath(e.path)}}:{}})}};import{FileMetadata as I,FileTrie as U,initWasm as A}from"@storagehub-sdk/core";var b=class extends c{getFileInfo(t,e,n){let r=this.withAuth(),o=`/buckets/${encodeURIComponent(t)}/info/${encodeURIComponent(e)}`;return this.ctx.http.get(o,{...r?{headers:r}:{},...n?{signal:n}:{}}).then(s=>({...s,uploadedAt:new Date(s.uploadedAt)}))}async uploadFile(t,e,n,r,o,s){await A();let i=`/buckets/${encodeURIComponent(t)}/upload/${encodeURIComponent(e)}`,a=this.withAuth(),l=await this.coerceToFormPart(n),u=l.size,w=await this.computeFileFingerprint(l),m=await this.formFileMetadata(r,t,o,w,BigInt(u)),d=await this.computeFileKey(m),h=this.hexToBytes(e);if(d.length!==h.length||!d.every((R,S)=>R===h[S]))throw new Error(`Computed file key ${d.toString()} does not match provided file key ${h.toString()}`);let P=m.encode(),g=new FormData,B=new Blob([new Uint8Array(P)],{type:"application/octet-stream"});return g.append("file_metadata",B,"file_metadata"),g.append("file",l,"file"),await this.ctx.http.put(i,a?{body:g,headers:a}:{body:g})}async downloadFile(t,e){let n=`/download/${encodeURIComponent(t)}`,r={Accept:"*/*"};if(e?.range){let{start:m,end:d}=e.range,h=`bytes=${m}-${d??""}`;r.Range=h}let o=this.withAuth(r),s=await this.ctx.http.getRaw(n,{...o?{headers:o}:{},...e?.signal?{signal:e.signal}:{}});if(!s.body)throw new Error("Response body is null - unable to create stream");let i=s.headers.get("content-type"),a=s.headers.get("content-range"),l=s.headers.get("content-length"),u=l!==null?Number(l):void 0,w=typeof u=="number"&&Number.isFinite(u)?u:null;return{stream:s.body,status:s.status,contentType:i,contentRange:a,contentLength:w}}async coerceToFormPart(t){if(typeof Blob<"u"&&t instanceof Blob)return t;if(t instanceof Uint8Array)return new Blob([t.buffer]);if(typeof ArrayBuffer<"u"&&t instanceof ArrayBuffer)return new Blob([t]);if(t instanceof ReadableStream){let e=t.getReader(),n=[],r=0;try{for(;;){let{done:i,value:a}=await e.read();if(i)break;a&&(n.push(a),r+=a.length)}}finally{e.releaseLock()}let o=new Uint8Array(r),s=0;for(let i of n)o.set(i,s),s+=i.length;return new Blob([o],{type:"application/octet-stream"})}return new Blob([t],{type:"application/octet-stream"})}async computeFileFingerprint(t){let e=new U,n=new Uint8Array(await t.arrayBuffer()),r=1024,o=0;for(;o<n.length;){let s=Math.min(o+r,n.length),i=n.slice(o,s);e.push_chunk(i),o=s}return e.get_root()}async formFileMetadata(t,e,n,r,o){let s=this.hexToBytes(t),i=this.hexToBytes(e),a=new TextEncoder().encode(n);return await A(),new I(s,i,a,o,r)}hexToBytes(t){if(!t)throw new Error("hex string cannot be empty");let e=t.startsWith("0x")?t.slice(2):t;if(e.length%2!==0)throw new Error("hex string must have an even number of characters");if(!/^[0-9a-fA-F]*$/.test(e))throw new Error("hex string contains invalid characters");return new Uint8Array(e.match(/.{2}/g)?.map(n=>Number.parseInt(n,16))||[])}async computeFileKey(t){return await A(),t.getFileKey()}};var x=class extends c{getHealth(t){return this.ctx.http.get("/health",{...t?{signal:t}:{}})}getInfo(t){return this.ctx.http.get("/info",{...t?{signal:t}:{}})}getStats(t){return this.ctx.http.get("/stats",{...t?{signal:t}:{}})}getValuePropositions(t){return this.ctx.http.get("/value-props",{...t?{signal:t}:{}})}getPaymentStreams(t){let e=this.withAuth();return this.ctx.http.get("/payment_streams",{...e?{headers:e}:{},...t?{signal:t}:{}})}};var k=class p extends c{config;context;auth;buckets;files;info;constructor(t,e){let n={config:t,http:e};super(n),this.config=t,this.context=n,this.auth=new f(this.context),this.buckets=new y(this.context),this.files=new b(this.context),this.info=new x(this.context)}static async connect(t){if(!t?.baseUrl)throw new Error("MspClient.connect: baseUrl is required");let e=new F({baseUrl:t.baseUrl,...t.timeoutMs!==void 0&&{timeoutMs:t.timeoutMs},...t.defaultHeaders!==void 0&&{defaultHeaders:t.defaultHeaders},...t.fetchImpl!==void 0&&{fetchImpl:t.fetchImpl}});return new p(t,e)}};export{k as MspClient};
|
|
2
2
|
//# sourceMappingURL=index.browser.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/MspClient.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n Bucket,\n DownloadOptions,\n DownloadResult,\n FileInfo,\n FileListResponse,\n GetFilesOptions,\n HealthStatus,\n InfoResponse,\n NonceResponse,\n StatsResponse,\n UploadOptions,\n UploadReceipt,\n ValueProp,\n VerifyResponse,\n} from './types.js';\nimport type { HttpClientConfig } from '@storagehub-sdk/core';\nimport { FileMetadata, FileTrie, HttpClient, initWasm } from '@storagehub-sdk/core';\n\nexport class MspClient {\n public readonly config: HttpClientConfig;\n private readonly http: HttpClient;\n public token?: string;\n\n private constructor(config: HttpClientConfig, http: HttpClient) {\n this.config = config;\n this.http = http;\n }\n\n static async connect(config: HttpClientConfig): 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 && { defaultHeaders: config.defaultHeaders }),\n ...(config.fetchImpl !== undefined && { fetchImpl: config.fetchImpl }),\n });\n\n return new MspClient(config, http);\n }\n\n getHealth(options?: { signal?: AbortSignal }): Promise<HealthStatus> {\n return this.http.get<HealthStatus>('/health', {\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n /** Get general MSP information */\n getInfo(options?: { signal?: AbortSignal }): Promise<InfoResponse> {\n return this.http.get<InfoResponse>('/info', {\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n /** Get MSP statistics */\n getStats(options?: { signal?: AbortSignal }): Promise<StatsResponse> {\n return this.http.get<StatsResponse>('/stats', {\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n /** Get available value propositions */\n getValuePropositions(options?: { signal?: AbortSignal }): Promise<ValueProp[]> {\n return this.http.get<ValueProp[]>('/value-props', {\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n // Auth endpoints:\n\n /** Request a SIWE-style nonce message for the given address and chainId */\n getNonce(\n address: string,\n chainId: number,\n options?: { signal?: AbortSignal },\n ): Promise<NonceResponse> {\n return this.http.post<NonceResponse>('/auth/nonce', {\n body: { address, chainId },\n headers: { 'Content-Type': 'application/json' },\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n /** Verify signed message and receive JWT token */\n verify(\n message: string,\n signature: string,\n options?: { signal?: AbortSignal },\n ): Promise<VerifyResponse> {\n return this.http.post<VerifyResponse>('/auth/verify', {\n body: { message, signature },\n headers: { 'Content-Type': 'application/json' },\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n /** Store token to be sent on subsequent protected requests */\n setToken(token: string): void {\n this.token = token;\n }\n\n /** Merge Authorization header when token is present */\n private withAuth(headers?: Record<string, string>): Record<string, string> | undefined {\n if (!this.token) return headers;\n return { ...(headers ?? {}), Authorization: `Bearer ${this.token}` };\n }\n\n // Bucket endpoints:\n\n /** List all buckets for the current authenticateduser */\n listBuckets(options?: { signal?: AbortSignal }): Promise<Bucket[]> {\n const headers = this.withAuth();\n return this.http.get<Bucket[]>('/buckets', {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n });\n }\n\n /** Get a specific bucket's metadata by its bucket ID */\n getBucket(bucketId: string, options?: { signal?: AbortSignal }): Promise<Bucket> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}`;\n return this.http.get<Bucket>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n });\n }\n\n /** Gets the list of files and folders under the specified path for a bucket. If no path is provided, it returns the files and folders found at root. */\n getFiles(bucketId: string, options?: GetFilesOptions): Promise<FileListResponse> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/files`;\n return this.http.get<FileListResponse>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n ...(options?.path ? { query: { path: options.path.replace(/^\\/+/, '') } } : {}),\n });\n }\n\n /** Get metadata for a file in a bucket by fileKey */\n getFileInfo(\n bucketId: string,\n fileKey: string,\n options?: { signal?: AbortSignal },\n ): Promise<FileInfo> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/info/${encodeURIComponent(fileKey)}`;\n type FileInfoWire = Omit<FileInfo, 'uploadedAt'> & { uploadedAt: string };\n return this.http\n .get<FileInfoWire>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n })\n .then((wire): FileInfo => ({ ...wire, uploadedAt: new Date(wire.uploadedAt) }));\n }\n\n // File endpoints:\n\n /**\n * Upload a file to a bucket with a specific key.\n *\n * Always uses multipart/form-data upload with both file data and encoded FileMetadata.\n * The file data is loaded into memory to create the multipart request.\n *\n */\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 = 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 // TODO: We should instead use FileManager here and use its `getFingerprint` method.\n // This would allow us to remove the `initWasm` call at the top and to stream the file\n // instead of loading it into memory as a blob.\n const fingerprint = await this.computeFileFingerprint(fileBlob);\n\n // Create the FileMetadata instance\n const metadata = await this.formFileMetadata(\n owner,\n bucketId,\n location,\n fingerprint,\n BigInt(fileSize),\n );\n\n // Compute the file key and ensure it matches the provided file key\n const computedFileKey = await this.computeFileKey(metadata);\n const expectedFileKeyBytes = this.hexToBytes(fileKey);\n if (\n computedFileKey.length !== expectedFileKeyBytes.length ||\n !computedFileKey.every((byte, index) => byte === expectedFileKeyBytes[index])\n ) {\n throw new Error(\n `Computed file key ${computedFileKey.toString()} does not match provided file key ${expectedFileKeyBytes.toString()}`,\n );\n }\n\n // Encode the file metadata\n const encodedMetadata = metadata.encode();\n\n // Create the multipart form with both the file and its metadata\n const form = new FormData();\n const fileMetadataBlob = new Blob([new Uint8Array(encodedMetadata)], {\n type: 'application/octet-stream',\n });\n form.append('file_metadata', fileMetadataBlob, 'file_metadata');\n form.append('file', fileBlob, 'file');\n\n const res = await this.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 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 async formFileMetadata(\n owner: string,\n bucketId: string,\n location: string,\n fingerprint: Uint8Array,\n size: bigint,\n ): Promise<FileMetadata> {\n const ownerBytes = this.hexToBytes(owner);\n const bucketIdBytes = this.hexToBytes(bucketId);\n const locationBytes = new TextEncoder().encode(location);\n await initWasm();\n return new FileMetadata(ownerBytes, bucketIdBytes, locationBytes, size, fingerprint);\n }\n\n hexToBytes(hex: string): Uint8Array {\n if (!hex) {\n throw new Error('hex string cannot be empty');\n }\n\n const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;\n\n if (cleanHex.length % 2 !== 0) {\n throw new Error('hex string must have an even number of characters');\n }\n\n if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {\n throw new Error('hex string contains invalid characters');\n }\n\n return new Uint8Array(cleanHex.match(/.{2}/g)?.map((byte) => Number.parseInt(byte, 16)) || []);\n }\n\n async computeFileKey(fileMetadata: FileMetadata): Promise<Uint8Array> {\n await initWasm();\n return fileMetadata.getFileKey();\n }\n\n /** Download a file by key. */\n async downloadByKey(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 const headers = this.withAuth(baseHeaders);\n const res = await this.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 }\n\n /** Download a file by its location path under a bucket. */\n async downloadByLocation(\n bucketId: string,\n filePath: string,\n options?: DownloadOptions,\n ): Promise<DownloadResult> {\n const normalized = filePath.replace(/^\\/+/, '');\n const encodedPath = normalized.split('/').map(encodeURIComponent).join('/');\n const path = `/buckets/${encodeURIComponent(bucketId)}/download/path/${encodedPath}`;\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 const headers = this.withAuth(baseHeaders);\n const res = await this.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 }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": ["
|
|
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\";\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(config: HttpClientConfig, http: HttpClient) {\n const context: MspClientContext = { config, http };\n super(context);\n this.config = config;\n this.context = context;\n this.auth = new AuthModule(this.context);\n this.buckets = new BucketsModule(this.context);\n this.files = new FilesModule(this.context);\n this.info = new InfoModule(this.context);\n }\n\n static async connect(config: HttpClientConfig): 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 return new MspClient(config, http);\n }\n}\n", "import type { NonceResponse, Session, UserInfo, AuthStatus } from \"../types.js\";\nimport { AuthState } from \"../types.js\";\nimport { getAddress, type WalletClient } from \"viem\";\nimport { ModuleBase } from \"../base.js\";\n\nexport class AuthModule extends ModuleBase {\n /**\n * Request nonce for SIWE.\n * - Input: EVM `address`, `chainId`.\n * - Output: message to sign.\n */\n private getNonce(address: string, chainId: number, signal?: AbortSignal): Promise<NonceResponse> {\n return this.ctx.http.post<NonceResponse>(\"/auth/nonce\", {\n body: { address, chainId },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n }\n\n /**\n * Verify SIWE signature.\n * - Persists `session` in context on success.\n */\n private async verify(message: string, signature: string, signal?: AbortSignal): Promise<Session> {\n const session = await this.ctx.http.post<Session>(\"/auth/verify\", {\n body: { message, signature },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n this.ctx.session = session;\n return session;\n }\n\n /**\n * Full SIWE flow using a `WalletClient`.\n * - Derives address, fetches nonce, signs message, verifies and stores session.\n */\n async SIWE(wallet: WalletClient, signal?: AbortSignal): Promise<void> {\n // Resolve the current active account from the WalletClient.\n // - Browser wallets (e.g., MetaMask) surface the user-selected address here.\n // - Viem/local wallets must set `wallet.account` explicitly before calling.\n const account = wallet.account;\n const resolvedAddress = typeof account === \"string\" ? account : account?.address;\n if (!resolvedAddress || !account) {\n throw new Error(\n \"Wallet client has no active account; set wallet.account before calling SIWE\"\n );\n }\n // Get the checksummed address\n const address = getAddress(resolvedAddress);\n const chainId = await wallet.getChainId();\n const { message } = await this.getNonce(address, chainId, signal);\n\n // Sign using the active account resolved above (string or Account object)\n const signature = await wallet.signMessage({ account, message });\n\n this.ctx.session = await this.verify(message, signature, signal);\n }\n\n /**\n * Fetch authenticated user's profile.\n * - Requires valid `session` (Authorization header added automatically).\n */\n getProfile(signal?: AbortSignal): Promise<UserInfo> {\n const headers = this.withAuth();\n return this.ctx.http.get<UserInfo>(\"/auth/profile\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n\n /**\n * Determine auth status by checking token presence and profile reachability.\n */\n async getAuthStatus(): Promise<AuthStatus> {\n if (!this.ctx.session?.token) {\n return { status: AuthState.NotAuthenticated };\n }\n const profile = await this.getProfile().catch((err: any) =>\n err?.response?.status === 401 ? null : Promise.reject(err)\n );\n return profile ? { status: AuthState.Authenticated } : { status: AuthState.TokenExpired };\n }\n}\n", "import type { MspClientContext } from \"./context.js\";\n\nexport abstract class ModuleBase {\n protected readonly ctx: MspClientContext;\n\n constructor(ctx: MspClientContext) {\n this.ctx = ctx;\n }\n\n protected withAuth(headers?: Record<string, string>): Record<string, string> | undefined {\n const token = this.ctx.session?.token;\n return token ? { ...(headers ?? {}), Authorization: `Bearer ${token}` } : headers;\n }\n\n /**\n * Normalize a user-provided path for HTTP query usage.\n * - Removes all leading '/' characters to avoid double slashes in URLs.\n * - Collapses any repeated slashes in the middle or at the end to a single '/'.\n * Examples:\n * \"/foo/bar\" -> \"foo/bar\"\n * \"///docs\" -> \"docs\"\n * \"foo//bar\" -> \"foo/bar\"\n * \"///a//b///\" -> \"a/b/\"\n * \"foo/bar\" -> \"foo/bar\" (unchanged)\n * \"/\" -> \"\"\n */\n protected normalizePath(path: string): string {\n // Drop leading slashes (offset === 0), collapse others to '/'\n return path.replace(/^\\/+|\\/{2,}/g, (_m, offset: number) => (offset === 0 ? \"\" : \"/\"));\n }\n}\n", "import type { Bucket, FileListResponse, GetFilesOptions } from \"../types.js\";\nimport { ModuleBase } from \"../base.js\";\n\nexport class BucketsModule extends ModuleBase {\n /** List all buckets for the current authenticated user */\n listBuckets(signal?: AbortSignal): Promise<Bucket[]> {\n const headers = this.withAuth();\n return this.ctx.http.get<Bucket[]>(\"/buckets\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get a specific bucket's metadata by its bucket ID */\n getBucket(bucketId: string, signal?: AbortSignal): Promise<Bucket> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}`;\n return this.ctx.http.get<Bucket>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n\n /** List files/folders under a path for a bucket (root if no path) */\n getFiles(bucketId: string, options?: GetFilesOptions): Promise<FileListResponse> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/files`;\n return this.ctx.http.get<FileListResponse>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n ...(options?.path ? { query: { path: this.normalizePath(options.path) } } : {})\n });\n }\n}\n", "import { ModuleBase } from \"../base.js\";\nimport type {\n DownloadOptions,\n DownloadResult,\n FileInfo,\n UploadOptions,\n UploadReceipt\n} from \"../types.js\";\nimport { FileMetadata, FileTrie, initWasm } from \"@storagehub-sdk/core\";\n\nexport class FilesModule extends ModuleBase {\n /** Get metadata for a file in a bucket by fileKey */\n getFileInfo(bucketId: string, fileKey: string, signal?: AbortSignal): Promise<FileInfo> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/info/${encodeURIComponent(fileKey)}`;\n type FileInfoWire = Omit<FileInfo, \"uploadedAt\"> & { uploadedAt: string };\n return this.ctx.http\n .get<FileInfoWire>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n })\n .then(\n (wire): FileInfo => ({\n ...wire,\n uploadedAt: new Date(wire.uploadedAt)\n })\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 = this.withAuth();\n\n // Convert the file to a blob and get its size\n const fileBlob = await this.coerceToFormPart(file);\n const fileSize = fileBlob.size;\n\n // Compute the fingerprint first\n const fingerprint = await this.computeFileFingerprint(fileBlob);\n\n // Create the FileMetadata instance\n const metadata = await this.formFileMetadata(\n owner,\n bucketId,\n location,\n fingerprint,\n BigInt(fileSize)\n );\n\n // Compute the file key and ensure it matches the provided file key\n const computedFileKey = await this.computeFileKey(metadata);\n const expectedFileKeyBytes = this.hexToBytes(fileKey);\n if (\n computedFileKey.length !== expectedFileKeyBytes.length ||\n !computedFileKey.every((byte, index) => byte === expectedFileKeyBytes[index])\n ) {\n throw new Error(\n `Computed file key ${computedFileKey.toString()} does not match provided file key ${expectedFileKeyBytes.toString()}`\n );\n }\n\n // Encode the file metadata\n const encodedMetadata = metadata.encode();\n\n // Create the multipart form with both the file and its metadata\n const form = new FormData();\n const fileMetadataBlob = new Blob([new Uint8Array(encodedMetadata)], {\n type: \"application/octet-stream\"\n });\n form.append(\"file_metadata\", fileMetadataBlob, \"file_metadata\");\n form.append(\"file\", fileBlob, \"file\");\n\n const res = await this.ctx.http.put<UploadReceipt>(\n backendPath,\n authHeaders\n ? { body: form as unknown as BodyInit, headers: authHeaders }\n : { body: form as unknown as BodyInit }\n );\n return res;\n }\n\n /** Download a file by key */\n async downloadFile(fileKey: string, options?: DownloadOptions): Promise<DownloadResult> {\n const path = `/download/${encodeURIComponent(fileKey)}`;\n const baseHeaders: Record<string, string> = { Accept: \"*/*\" };\n if (options?.range) {\n const { start, end } = options.range;\n const rangeValue = `bytes=${start}-${end ?? \"\"}`;\n baseHeaders.Range = rangeValue;\n }\n const headers = this.withAuth(baseHeaders);\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 }\n\n // Helpers\n private async coerceToFormPart(\n file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown\n ): Promise<Blob> {\n if (typeof Blob !== \"undefined\" && file instanceof Blob) return file;\n if (file instanceof Uint8Array) return new Blob([file.buffer as ArrayBuffer]);\n if (typeof ArrayBuffer !== \"undefined\" && file instanceof ArrayBuffer) return new Blob([file]);\n\n // Handle ReadableStream by reading it into memory\n if (file instanceof ReadableStream) {\n const reader = file.getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n totalLength += value.length;\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n // Combine all chunks into a single Uint8Array\n const combined = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n return new Blob([combined], { type: \"application/octet-stream\" });\n }\n\n return new Blob([file as BlobPart], { type: \"application/octet-stream\" });\n }\n\n private async computeFileFingerprint(fileBlob: Blob): Promise<Uint8Array> {\n const trie = new FileTrie();\n const fileBytes = new Uint8Array(await fileBlob.arrayBuffer());\n\n // Process the file in 1KB chunks (matching CHUNK_SIZE from constants)\n const CHUNK_SIZE = 1024;\n let offset = 0;\n\n while (offset < fileBytes.length) {\n const end = Math.min(offset + CHUNK_SIZE, fileBytes.length);\n const chunk = fileBytes.slice(offset, end);\n trie.push_chunk(chunk);\n offset = end;\n }\n\n return trie.get_root();\n }\n\n private async formFileMetadata(\n owner: string,\n bucketId: string,\n location: string,\n fingerprint: Uint8Array,\n size: bigint\n ): Promise<FileMetadata> {\n const ownerBytes = this.hexToBytes(owner);\n const bucketIdBytes = this.hexToBytes(bucketId);\n const locationBytes = new TextEncoder().encode(location);\n await initWasm();\n return new FileMetadata(ownerBytes, bucketIdBytes, locationBytes, size, fingerprint);\n }\n\n private hexToBytes(hex: string): Uint8Array {\n if (!hex) {\n throw new Error(\"hex string cannot be empty\");\n }\n\n const cleanHex = hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n\n if (cleanHex.length % 2 !== 0) {\n throw new Error(\"hex string must have an even number of characters\");\n }\n\n if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {\n throw new Error(\"hex string contains invalid characters\");\n }\n\n return new Uint8Array(cleanHex.match(/.{2}/g)?.map((byte) => Number.parseInt(byte, 16)) || []);\n }\n\n private async computeFileKey(fileMetadata: FileMetadata): Promise<Uint8Array> {\n await initWasm();\n return fileMetadata.getFileKey();\n }\n}\n", "import { ModuleBase } from \"../base.js\";\nimport type {\n HealthStatus,\n InfoResponse,\n PaymentStreamsResponse,\n StatsResponse,\n ValueProp\n} from \"../types.js\";\n\nexport class InfoModule extends ModuleBase {\n getHealth(signal?: AbortSignal): Promise<HealthStatus> {\n return this.ctx.http.get<HealthStatus>(\"/health\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get general MSP information */\n getInfo(signal?: AbortSignal): Promise<InfoResponse> {\n return this.ctx.http.get<InfoResponse>(\"/info\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get MSP statistics */\n getStats(signal?: AbortSignal): Promise<StatsResponse> {\n return this.ctx.http.get<StatsResponse>(\"/stats\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get available value propositions */\n getValuePropositions(signal?: AbortSignal): Promise<ValueProp[]> {\n return this.ctx.http.get<ValueProp[]>(\"/value-props\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get payment streams for current authenticated user */\n getPaymentStreams(signal?: AbortSignal): Promise<PaymentStreamsResponse> {\n const headers = 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", "headers", "token", "path", "_m", "offset", "AuthModule", "ModuleBase", "address", "chainId", "signal", "message", "signature", "session", "wallet", "account", "resolvedAddress", "getAddress", "headers", "err", "BucketsModule", "ModuleBase", "signal", "headers", "bucketId", "path", "options", "FileMetadata", "FileTrie", "initWasm", "FilesModule", "ModuleBase", "bucketId", "fileKey", "signal", "headers", "path", "wire", "file", "owner", "location", "_options", "backendPath", "authHeaders", "fileBlob", "fileSize", "fingerprint", "metadata", "computedFileKey", "expectedFileKeyBytes", "byte", "index", "encodedMetadata", "form", "fileMetadataBlob", "options", "baseHeaders", "start", "end", "rangeValue", "res", "contentType", "contentRange", "contentLengthHeader", "parsedLength", "contentLength", "reader", "chunks", "totalLength", "done", "value", "combined", "offset", "chunk", "trie", "fileBytes", "CHUNK_SIZE", "size", "ownerBytes", "bucketIdBytes", "locationBytes", "hex", "cleanHex", "fileMetadata", "InfoModule", "ModuleBase", "signal", "headers", "MspClient", "_MspClient", "ModuleBase", "config", "http", "context", "AuthModule", "BucketsModule", "FilesModule", "InfoModule", "HttpClient"]
|
|
7
7
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { MspClient } from
|
|
2
|
-
export type { Bucket, Capacity, DownloadOptions, DownloadResult,
|
|
1
|
+
export { MspClient } from "./MspClient.js";
|
|
2
|
+
export type { Bucket, Capacity, DownloadOptions, DownloadResult, FileTree, FileInfo, FileListResponse, HealthStatus, InfoResponse, NonceResponse, AuthState, AuthStatus, UserInfo, PaymentStreamInfo, PaymentStreamsResponse, StatsResponse, UploadOptions, UploadReceipt, ValueProp, Session } from "./types.js";
|
package/dist/index.node.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{HttpClient as F}from"@storagehub-sdk/core";import{getAddress as C}from"viem";var c=class{ctx;constructor(t){this.ctx=t}withAuth(t){let e=this.ctx.session?.token;return e?{...t??{},Authorization:`Bearer ${e}`}:t}normalizePath(t){return t.replace(/^\/+|\/{2,}/g,(e,n)=>n===0?"":"/")}};var f=class extends c{getNonce(t,e,n){return this.ctx.http.post("/auth/nonce",{body:{address:t,chainId:e},headers:{"Content-Type":"application/json"},...n?{signal:n}:{}})}async verify(t,e,n){let r=await this.ctx.http.post("/auth/verify",{body:{message:t,signature:e},headers:{"Content-Type":"application/json"},...n?{signal:n}:{}});return this.ctx.session=r,r}async SIWE(t,e){let n=t.account,r=typeof n=="string"?n:n?.address;if(!r||!n)throw new Error("Wallet client has no active account; set wallet.account before calling SIWE");let o=C(r),s=await t.getChainId(),{message:i}=await this.getNonce(o,s,e),a=await t.signMessage({account:n,message:i});this.ctx.session=await this.verify(i,a,e)}getProfile(t){let e=this.withAuth();return this.ctx.http.get("/auth/profile",{...e?{headers:e}:{},...t?{signal:t}:{}})}async getAuthStatus(){return this.ctx.session?.token?await this.getProfile().catch(e=>e?.response?.status===401?null:Promise.reject(e))?{status:"Authenticated"}:{status:"TokenExpired"}:{status:"NotAuthenticated"}}};var y=class extends c{listBuckets(t){let e=this.withAuth();return this.ctx.http.get("/buckets",{...e?{headers:e}:{},...t?{signal:t}:{}})}getBucket(t,e){let n=this.withAuth(),r=`/buckets/${encodeURIComponent(t)}`;return this.ctx.http.get(r,{...n?{headers:n}:{},...e?{signal:e}:{}})}getFiles(t,e){let n=this.withAuth(),r=`/buckets/${encodeURIComponent(t)}/files`;return this.ctx.http.get(r,{...n?{headers:n}:{},...e?.signal?{signal:e.signal}:{},...e?.path?{query:{path:this.normalizePath(e.path)}}:{}})}};import{FileMetadata as I,FileTrie as U,initWasm as A}from"@storagehub-sdk/core";var b=class extends c{getFileInfo(t,e,n){let r=this.withAuth(),o=`/buckets/${encodeURIComponent(t)}/info/${encodeURIComponent(e)}`;return this.ctx.http.get(o,{...r?{headers:r}:{},...n?{signal:n}:{}}).then(s=>({...s,uploadedAt:new Date(s.uploadedAt)}))}async uploadFile(t,e,n,r,o,s){await A();let i=`/buckets/${encodeURIComponent(t)}/upload/${encodeURIComponent(e)}`,a=this.withAuth(),l=await this.coerceToFormPart(n),u=l.size,w=await this.computeFileFingerprint(l),m=await this.formFileMetadata(r,t,o,w,BigInt(u)),d=await this.computeFileKey(m),h=this.hexToBytes(e);if(d.length!==h.length||!d.every((R,S)=>R===h[S]))throw new Error(`Computed file key ${d.toString()} does not match provided file key ${h.toString()}`);let P=m.encode(),g=new FormData,B=new Blob([new Uint8Array(P)],{type:"application/octet-stream"});return g.append("file_metadata",B,"file_metadata"),g.append("file",l,"file"),await this.ctx.http.put(i,a?{body:g,headers:a}:{body:g})}async downloadFile(t,e){let n=`/download/${encodeURIComponent(t)}`,r={Accept:"*/*"};if(e?.range){let{start:m,end:d}=e.range,h=`bytes=${m}-${d??""}`;r.Range=h}let o=this.withAuth(r),s=await this.ctx.http.getRaw(n,{...o?{headers:o}:{},...e?.signal?{signal:e.signal}:{}});if(!s.body)throw new Error("Response body is null - unable to create stream");let i=s.headers.get("content-type"),a=s.headers.get("content-range"),l=s.headers.get("content-length"),u=l!==null?Number(l):void 0,w=typeof u=="number"&&Number.isFinite(u)?u:null;return{stream:s.body,status:s.status,contentType:i,contentRange:a,contentLength:w}}async coerceToFormPart(t){if(typeof Blob<"u"&&t instanceof Blob)return t;if(t instanceof Uint8Array)return new Blob([t.buffer]);if(typeof ArrayBuffer<"u"&&t instanceof ArrayBuffer)return new Blob([t]);if(t instanceof ReadableStream){let e=t.getReader(),n=[],r=0;try{for(;;){let{done:i,value:a}=await e.read();if(i)break;a&&(n.push(a),r+=a.length)}}finally{e.releaseLock()}let o=new Uint8Array(r),s=0;for(let i of n)o.set(i,s),s+=i.length;return new Blob([o],{type:"application/octet-stream"})}return new Blob([t],{type:"application/octet-stream"})}async computeFileFingerprint(t){let e=new U,n=new Uint8Array(await t.arrayBuffer()),r=1024,o=0;for(;o<n.length;){let s=Math.min(o+r,n.length),i=n.slice(o,s);e.push_chunk(i),o=s}return e.get_root()}async formFileMetadata(t,e,n,r,o){let s=this.hexToBytes(t),i=this.hexToBytes(e),a=new TextEncoder().encode(n);return await A(),new I(s,i,a,o,r)}hexToBytes(t){if(!t)throw new Error("hex string cannot be empty");let e=t.startsWith("0x")?t.slice(2):t;if(e.length%2!==0)throw new Error("hex string must have an even number of characters");if(!/^[0-9a-fA-F]*$/.test(e))throw new Error("hex string contains invalid characters");return new Uint8Array(e.match(/.{2}/g)?.map(n=>Number.parseInt(n,16))||[])}async computeFileKey(t){return await A(),t.getFileKey()}};var x=class extends c{getHealth(t){return this.ctx.http.get("/health",{...t?{signal:t}:{}})}getInfo(t){return this.ctx.http.get("/info",{...t?{signal:t}:{}})}getStats(t){return this.ctx.http.get("/stats",{...t?{signal:t}:{}})}getValuePropositions(t){return this.ctx.http.get("/value-props",{...t?{signal:t}:{}})}getPaymentStreams(t){let e=this.withAuth();return this.ctx.http.get("/payment_streams",{...e?{headers:e}:{},...t?{signal:t}:{}})}};var k=class p extends c{config;context;auth;buckets;files;info;constructor(t,e){let n={config:t,http:e};super(n),this.config=t,this.context=n,this.auth=new f(this.context),this.buckets=new y(this.context),this.files=new b(this.context),this.info=new x(this.context)}static async connect(t){if(!t?.baseUrl)throw new Error("MspClient.connect: baseUrl is required");let e=new F({baseUrl:t.baseUrl,...t.timeoutMs!==void 0&&{timeoutMs:t.timeoutMs},...t.defaultHeaders!==void 0&&{defaultHeaders:t.defaultHeaders},...t.fetchImpl!==void 0&&{fetchImpl:t.fetchImpl}});return new p(t,e)}};export{k 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
|
-
"sources": ["../src/MspClient.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n Bucket,\n DownloadOptions,\n DownloadResult,\n FileInfo,\n FileListResponse,\n GetFilesOptions,\n HealthStatus,\n InfoResponse,\n NonceResponse,\n StatsResponse,\n UploadOptions,\n UploadReceipt,\n ValueProp,\n VerifyResponse,\n} from './types.js';\nimport type { HttpClientConfig } from '@storagehub-sdk/core';\nimport { FileMetadata, FileTrie, HttpClient, initWasm } from '@storagehub-sdk/core';\n\nexport class MspClient {\n public readonly config: HttpClientConfig;\n private readonly http: HttpClient;\n public token?: string;\n\n private constructor(config: HttpClientConfig, http: HttpClient) {\n this.config = config;\n this.http = http;\n }\n\n static async connect(config: HttpClientConfig): 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 && { defaultHeaders: config.defaultHeaders }),\n ...(config.fetchImpl !== undefined && { fetchImpl: config.fetchImpl }),\n });\n\n return new MspClient(config, http);\n }\n\n getHealth(options?: { signal?: AbortSignal }): Promise<HealthStatus> {\n return this.http.get<HealthStatus>('/health', {\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n /** Get general MSP information */\n getInfo(options?: { signal?: AbortSignal }): Promise<InfoResponse> {\n return this.http.get<InfoResponse>('/info', {\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n /** Get MSP statistics */\n getStats(options?: { signal?: AbortSignal }): Promise<StatsResponse> {\n return this.http.get<StatsResponse>('/stats', {\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n /** Get available value propositions */\n getValuePropositions(options?: { signal?: AbortSignal }): Promise<ValueProp[]> {\n return this.http.get<ValueProp[]>('/value-props', {\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n // Auth endpoints:\n\n /** Request a SIWE-style nonce message for the given address and chainId */\n getNonce(\n address: string,\n chainId: number,\n options?: { signal?: AbortSignal },\n ): Promise<NonceResponse> {\n return this.http.post<NonceResponse>('/auth/nonce', {\n body: { address, chainId },\n headers: { 'Content-Type': 'application/json' },\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n /** Verify signed message and receive JWT token */\n verify(\n message: string,\n signature: string,\n options?: { signal?: AbortSignal },\n ): Promise<VerifyResponse> {\n return this.http.post<VerifyResponse>('/auth/verify', {\n body: { message, signature },\n headers: { 'Content-Type': 'application/json' },\n ...(options?.signal !== undefined && { signal: options.signal }),\n });\n }\n\n /** Store token to be sent on subsequent protected requests */\n setToken(token: string): void {\n this.token = token;\n }\n\n /** Merge Authorization header when token is present */\n private withAuth(headers?: Record<string, string>): Record<string, string> | undefined {\n if (!this.token) return headers;\n return { ...(headers ?? {}), Authorization: `Bearer ${this.token}` };\n }\n\n // Bucket endpoints:\n\n /** List all buckets for the current authenticateduser */\n listBuckets(options?: { signal?: AbortSignal }): Promise<Bucket[]> {\n const headers = this.withAuth();\n return this.http.get<Bucket[]>('/buckets', {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n });\n }\n\n /** Get a specific bucket's metadata by its bucket ID */\n getBucket(bucketId: string, options?: { signal?: AbortSignal }): Promise<Bucket> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}`;\n return this.http.get<Bucket>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n });\n }\n\n /** Gets the list of files and folders under the specified path for a bucket. If no path is provided, it returns the files and folders found at root. */\n getFiles(bucketId: string, options?: GetFilesOptions): Promise<FileListResponse> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/files`;\n return this.http.get<FileListResponse>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n ...(options?.path ? { query: { path: options.path.replace(/^\\/+/, '') } } : {}),\n });\n }\n\n /** Get metadata for a file in a bucket by fileKey */\n getFileInfo(\n bucketId: string,\n fileKey: string,\n options?: { signal?: AbortSignal },\n ): Promise<FileInfo> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/info/${encodeURIComponent(fileKey)}`;\n type FileInfoWire = Omit<FileInfo, 'uploadedAt'> & { uploadedAt: string };\n return this.http\n .get<FileInfoWire>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n })\n .then((wire): FileInfo => ({ ...wire, uploadedAt: new Date(wire.uploadedAt) }));\n }\n\n // File endpoints:\n\n /**\n * Upload a file to a bucket with a specific key.\n *\n * Always uses multipart/form-data upload with both file data and encoded FileMetadata.\n * The file data is loaded into memory to create the multipart request.\n *\n */\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 = 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 // TODO: We should instead use FileManager here and use its `getFingerprint` method.\n // This would allow us to remove the `initWasm` call at the top and to stream the file\n // instead of loading it into memory as a blob.\n const fingerprint = await this.computeFileFingerprint(fileBlob);\n\n // Create the FileMetadata instance\n const metadata = await this.formFileMetadata(\n owner,\n bucketId,\n location,\n fingerprint,\n BigInt(fileSize),\n );\n\n // Compute the file key and ensure it matches the provided file key\n const computedFileKey = await this.computeFileKey(metadata);\n const expectedFileKeyBytes = this.hexToBytes(fileKey);\n if (\n computedFileKey.length !== expectedFileKeyBytes.length ||\n !computedFileKey.every((byte, index) => byte === expectedFileKeyBytes[index])\n ) {\n throw new Error(\n `Computed file key ${computedFileKey.toString()} does not match provided file key ${expectedFileKeyBytes.toString()}`,\n );\n }\n\n // Encode the file metadata\n const encodedMetadata = metadata.encode();\n\n // Create the multipart form with both the file and its metadata\n const form = new FormData();\n const fileMetadataBlob = new Blob([new Uint8Array(encodedMetadata)], {\n type: 'application/octet-stream',\n });\n form.append('file_metadata', fileMetadataBlob, 'file_metadata');\n form.append('file', fileBlob, 'file');\n\n const res = await this.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 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 async formFileMetadata(\n owner: string,\n bucketId: string,\n location: string,\n fingerprint: Uint8Array,\n size: bigint,\n ): Promise<FileMetadata> {\n const ownerBytes = this.hexToBytes(owner);\n const bucketIdBytes = this.hexToBytes(bucketId);\n const locationBytes = new TextEncoder().encode(location);\n await initWasm();\n return new FileMetadata(ownerBytes, bucketIdBytes, locationBytes, size, fingerprint);\n }\n\n hexToBytes(hex: string): Uint8Array {\n if (!hex) {\n throw new Error('hex string cannot be empty');\n }\n\n const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;\n\n if (cleanHex.length % 2 !== 0) {\n throw new Error('hex string must have an even number of characters');\n }\n\n if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {\n throw new Error('hex string contains invalid characters');\n }\n\n return new Uint8Array(cleanHex.match(/.{2}/g)?.map((byte) => Number.parseInt(byte, 16)) || []);\n }\n\n async computeFileKey(fileMetadata: FileMetadata): Promise<Uint8Array> {\n await initWasm();\n return fileMetadata.getFileKey();\n }\n\n /** Download a file by key. */\n async downloadByKey(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 const headers = this.withAuth(baseHeaders);\n const res = await this.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 }\n\n /** Download a file by its location path under a bucket. */\n async downloadByLocation(\n bucketId: string,\n filePath: string,\n options?: DownloadOptions,\n ): Promise<DownloadResult> {\n const normalized = filePath.replace(/^\\/+/, '');\n const encodedPath = normalized.split('/').map(encodeURIComponent).join('/');\n const path = `/buckets/${encodeURIComponent(bucketId)}/download/path/${encodedPath}`;\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 const headers = this.withAuth(baseHeaders);\n const res = await this.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 }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": ["
|
|
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\";\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(config: HttpClientConfig, http: HttpClient) {\n const context: MspClientContext = { config, http };\n super(context);\n this.config = config;\n this.context = context;\n this.auth = new AuthModule(this.context);\n this.buckets = new BucketsModule(this.context);\n this.files = new FilesModule(this.context);\n this.info = new InfoModule(this.context);\n }\n\n static async connect(config: HttpClientConfig): 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 return new MspClient(config, http);\n }\n}\n", "import type { NonceResponse, Session, UserInfo, AuthStatus } from \"../types.js\";\nimport { AuthState } from \"../types.js\";\nimport { getAddress, type WalletClient } from \"viem\";\nimport { ModuleBase } from \"../base.js\";\n\nexport class AuthModule extends ModuleBase {\n /**\n * Request nonce for SIWE.\n * - Input: EVM `address`, `chainId`.\n * - Output: message to sign.\n */\n private getNonce(address: string, chainId: number, signal?: AbortSignal): Promise<NonceResponse> {\n return this.ctx.http.post<NonceResponse>(\"/auth/nonce\", {\n body: { address, chainId },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n }\n\n /**\n * Verify SIWE signature.\n * - Persists `session` in context on success.\n */\n private async verify(message: string, signature: string, signal?: AbortSignal): Promise<Session> {\n const session = await this.ctx.http.post<Session>(\"/auth/verify\", {\n body: { message, signature },\n headers: { \"Content-Type\": \"application/json\" },\n ...(signal ? { signal } : {})\n });\n this.ctx.session = session;\n return session;\n }\n\n /**\n * Full SIWE flow using a `WalletClient`.\n * - Derives address, fetches nonce, signs message, verifies and stores session.\n */\n async SIWE(wallet: WalletClient, signal?: AbortSignal): Promise<void> {\n // Resolve the current active account from the WalletClient.\n // - Browser wallets (e.g., MetaMask) surface the user-selected address here.\n // - Viem/local wallets must set `wallet.account` explicitly before calling.\n const account = wallet.account;\n const resolvedAddress = typeof account === \"string\" ? account : account?.address;\n if (!resolvedAddress || !account) {\n throw new Error(\n \"Wallet client has no active account; set wallet.account before calling SIWE\"\n );\n }\n // Get the checksummed address\n const address = getAddress(resolvedAddress);\n const chainId = await wallet.getChainId();\n const { message } = await this.getNonce(address, chainId, signal);\n\n // Sign using the active account resolved above (string or Account object)\n const signature = await wallet.signMessage({ account, message });\n\n this.ctx.session = await this.verify(message, signature, signal);\n }\n\n /**\n * Fetch authenticated user's profile.\n * - Requires valid `session` (Authorization header added automatically).\n */\n getProfile(signal?: AbortSignal): Promise<UserInfo> {\n const headers = this.withAuth();\n return this.ctx.http.get<UserInfo>(\"/auth/profile\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n\n /**\n * Determine auth status by checking token presence and profile reachability.\n */\n async getAuthStatus(): Promise<AuthStatus> {\n if (!this.ctx.session?.token) {\n return { status: AuthState.NotAuthenticated };\n }\n const profile = await this.getProfile().catch((err: any) =>\n err?.response?.status === 401 ? null : Promise.reject(err)\n );\n return profile ? { status: AuthState.Authenticated } : { status: AuthState.TokenExpired };\n }\n}\n", "import type { MspClientContext } from \"./context.js\";\n\nexport abstract class ModuleBase {\n protected readonly ctx: MspClientContext;\n\n constructor(ctx: MspClientContext) {\n this.ctx = ctx;\n }\n\n protected withAuth(headers?: Record<string, string>): Record<string, string> | undefined {\n const token = this.ctx.session?.token;\n return token ? { ...(headers ?? {}), Authorization: `Bearer ${token}` } : headers;\n }\n\n /**\n * Normalize a user-provided path for HTTP query usage.\n * - Removes all leading '/' characters to avoid double slashes in URLs.\n * - Collapses any repeated slashes in the middle or at the end to a single '/'.\n * Examples:\n * \"/foo/bar\" -> \"foo/bar\"\n * \"///docs\" -> \"docs\"\n * \"foo//bar\" -> \"foo/bar\"\n * \"///a//b///\" -> \"a/b/\"\n * \"foo/bar\" -> \"foo/bar\" (unchanged)\n * \"/\" -> \"\"\n */\n protected normalizePath(path: string): string {\n // Drop leading slashes (offset === 0), collapse others to '/'\n return path.replace(/^\\/+|\\/{2,}/g, (_m, offset: number) => (offset === 0 ? \"\" : \"/\"));\n }\n}\n", "import type { Bucket, FileListResponse, GetFilesOptions } from \"../types.js\";\nimport { ModuleBase } from \"../base.js\";\n\nexport class BucketsModule extends ModuleBase {\n /** List all buckets for the current authenticated user */\n listBuckets(signal?: AbortSignal): Promise<Bucket[]> {\n const headers = this.withAuth();\n return this.ctx.http.get<Bucket[]>(\"/buckets\", {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get a specific bucket's metadata by its bucket ID */\n getBucket(bucketId: string, signal?: AbortSignal): Promise<Bucket> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}`;\n return this.ctx.http.get<Bucket>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n });\n }\n\n /** List files/folders under a path for a bucket (root if no path) */\n getFiles(bucketId: string, options?: GetFilesOptions): Promise<FileListResponse> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/files`;\n return this.ctx.http.get<FileListResponse>(path, {\n ...(headers ? { headers } : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n ...(options?.path ? { query: { path: this.normalizePath(options.path) } } : {})\n });\n }\n}\n", "import { ModuleBase } from \"../base.js\";\nimport type {\n DownloadOptions,\n DownloadResult,\n FileInfo,\n UploadOptions,\n UploadReceipt\n} from \"../types.js\";\nimport { FileMetadata, FileTrie, initWasm } from \"@storagehub-sdk/core\";\n\nexport class FilesModule extends ModuleBase {\n /** Get metadata for a file in a bucket by fileKey */\n getFileInfo(bucketId: string, fileKey: string, signal?: AbortSignal): Promise<FileInfo> {\n const headers = this.withAuth();\n const path = `/buckets/${encodeURIComponent(bucketId)}/info/${encodeURIComponent(fileKey)}`;\n type FileInfoWire = Omit<FileInfo, \"uploadedAt\"> & { uploadedAt: string };\n return this.ctx.http\n .get<FileInfoWire>(path, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {})\n })\n .then(\n (wire): FileInfo => ({\n ...wire,\n uploadedAt: new Date(wire.uploadedAt)\n })\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 = this.withAuth();\n\n // Convert the file to a blob and get its size\n const fileBlob = await this.coerceToFormPart(file);\n const fileSize = fileBlob.size;\n\n // Compute the fingerprint first\n const fingerprint = await this.computeFileFingerprint(fileBlob);\n\n // Create the FileMetadata instance\n const metadata = await this.formFileMetadata(\n owner,\n bucketId,\n location,\n fingerprint,\n BigInt(fileSize)\n );\n\n // Compute the file key and ensure it matches the provided file key\n const computedFileKey = await this.computeFileKey(metadata);\n const expectedFileKeyBytes = this.hexToBytes(fileKey);\n if (\n computedFileKey.length !== expectedFileKeyBytes.length ||\n !computedFileKey.every((byte, index) => byte === expectedFileKeyBytes[index])\n ) {\n throw new Error(\n `Computed file key ${computedFileKey.toString()} does not match provided file key ${expectedFileKeyBytes.toString()}`\n );\n }\n\n // Encode the file metadata\n const encodedMetadata = metadata.encode();\n\n // Create the multipart form with both the file and its metadata\n const form = new FormData();\n const fileMetadataBlob = new Blob([new Uint8Array(encodedMetadata)], {\n type: \"application/octet-stream\"\n });\n form.append(\"file_metadata\", fileMetadataBlob, \"file_metadata\");\n form.append(\"file\", fileBlob, \"file\");\n\n const res = await this.ctx.http.put<UploadReceipt>(\n backendPath,\n authHeaders\n ? { body: form as unknown as BodyInit, headers: authHeaders }\n : { body: form as unknown as BodyInit }\n );\n return res;\n }\n\n /** Download a file by key */\n async downloadFile(fileKey: string, options?: DownloadOptions): Promise<DownloadResult> {\n const path = `/download/${encodeURIComponent(fileKey)}`;\n const baseHeaders: Record<string, string> = { Accept: \"*/*\" };\n if (options?.range) {\n const { start, end } = options.range;\n const rangeValue = `bytes=${start}-${end ?? \"\"}`;\n baseHeaders.Range = rangeValue;\n }\n const headers = this.withAuth(baseHeaders);\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 }\n\n // Helpers\n private async coerceToFormPart(\n file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown\n ): Promise<Blob> {\n if (typeof Blob !== \"undefined\" && file instanceof Blob) return file;\n if (file instanceof Uint8Array) return new Blob([file.buffer as ArrayBuffer]);\n if (typeof ArrayBuffer !== \"undefined\" && file instanceof ArrayBuffer) return new Blob([file]);\n\n // Handle ReadableStream by reading it into memory\n if (file instanceof ReadableStream) {\n const reader = file.getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(value);\n totalLength += value.length;\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n // Combine all chunks into a single Uint8Array\n const combined = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n return new Blob([combined], { type: \"application/octet-stream\" });\n }\n\n return new Blob([file as BlobPart], { type: \"application/octet-stream\" });\n }\n\n private async computeFileFingerprint(fileBlob: Blob): Promise<Uint8Array> {\n const trie = new FileTrie();\n const fileBytes = new Uint8Array(await fileBlob.arrayBuffer());\n\n // Process the file in 1KB chunks (matching CHUNK_SIZE from constants)\n const CHUNK_SIZE = 1024;\n let offset = 0;\n\n while (offset < fileBytes.length) {\n const end = Math.min(offset + CHUNK_SIZE, fileBytes.length);\n const chunk = fileBytes.slice(offset, end);\n trie.push_chunk(chunk);\n offset = end;\n }\n\n return trie.get_root();\n }\n\n private async formFileMetadata(\n owner: string,\n bucketId: string,\n location: string,\n fingerprint: Uint8Array,\n size: bigint\n ): Promise<FileMetadata> {\n const ownerBytes = this.hexToBytes(owner);\n const bucketIdBytes = this.hexToBytes(bucketId);\n const locationBytes = new TextEncoder().encode(location);\n await initWasm();\n return new FileMetadata(ownerBytes, bucketIdBytes, locationBytes, size, fingerprint);\n }\n\n private hexToBytes(hex: string): Uint8Array {\n if (!hex) {\n throw new Error(\"hex string cannot be empty\");\n }\n\n const cleanHex = hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n\n if (cleanHex.length % 2 !== 0) {\n throw new Error(\"hex string must have an even number of characters\");\n }\n\n if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {\n throw new Error(\"hex string contains invalid characters\");\n }\n\n return new Uint8Array(cleanHex.match(/.{2}/g)?.map((byte) => Number.parseInt(byte, 16)) || []);\n }\n\n private async computeFileKey(fileMetadata: FileMetadata): Promise<Uint8Array> {\n await initWasm();\n return fileMetadata.getFileKey();\n }\n}\n", "import { ModuleBase } from \"../base.js\";\nimport type {\n HealthStatus,\n InfoResponse,\n PaymentStreamsResponse,\n StatsResponse,\n ValueProp\n} from \"../types.js\";\n\nexport class InfoModule extends ModuleBase {\n getHealth(signal?: AbortSignal): Promise<HealthStatus> {\n return this.ctx.http.get<HealthStatus>(\"/health\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get general MSP information */\n getInfo(signal?: AbortSignal): Promise<InfoResponse> {\n return this.ctx.http.get<InfoResponse>(\"/info\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get MSP statistics */\n getStats(signal?: AbortSignal): Promise<StatsResponse> {\n return this.ctx.http.get<StatsResponse>(\"/stats\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get available value propositions */\n getValuePropositions(signal?: AbortSignal): Promise<ValueProp[]> {\n return this.ctx.http.get<ValueProp[]>(\"/value-props\", {\n ...(signal ? { signal } : {})\n });\n }\n\n /** Get payment streams for current authenticated user */\n getPaymentStreams(signal?: AbortSignal): Promise<PaymentStreamsResponse> {\n const headers = 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", "headers", "token", "path", "_m", "offset", "AuthModule", "ModuleBase", "address", "chainId", "signal", "message", "signature", "session", "wallet", "account", "resolvedAddress", "getAddress", "headers", "err", "BucketsModule", "ModuleBase", "signal", "headers", "bucketId", "path", "options", "FileMetadata", "FileTrie", "initWasm", "FilesModule", "ModuleBase", "bucketId", "fileKey", "signal", "headers", "path", "wire", "file", "owner", "location", "_options", "backendPath", "authHeaders", "fileBlob", "fileSize", "fingerprint", "metadata", "computedFileKey", "expectedFileKeyBytes", "byte", "index", "encodedMetadata", "form", "fileMetadataBlob", "options", "baseHeaders", "start", "end", "rangeValue", "res", "contentType", "contentRange", "contentLengthHeader", "parsedLength", "contentLength", "reader", "chunks", "totalLength", "done", "value", "combined", "offset", "chunk", "trie", "fileBytes", "CHUNK_SIZE", "size", "ownerBytes", "bucketIdBytes", "locationBytes", "hex", "cleanHex", "fileMetadata", "InfoModule", "ModuleBase", "signal", "headers", "MspClient", "_MspClient", "ModuleBase", "config", "http", "context", "AuthModule", "BucketsModule", "FilesModule", "InfoModule", "HttpClient"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { UserInfo, AuthStatus } from "../types.js";
|
|
2
|
+
import { type WalletClient } from "viem";
|
|
3
|
+
import { ModuleBase } from "../base.js";
|
|
4
|
+
export declare class AuthModule extends ModuleBase {
|
|
5
|
+
/**
|
|
6
|
+
* Request nonce for SIWE.
|
|
7
|
+
* - Input: EVM `address`, `chainId`.
|
|
8
|
+
* - Output: message to sign.
|
|
9
|
+
*/
|
|
10
|
+
private getNonce;
|
|
11
|
+
/**
|
|
12
|
+
* Verify SIWE signature.
|
|
13
|
+
* - Persists `session` in context on success.
|
|
14
|
+
*/
|
|
15
|
+
private verify;
|
|
16
|
+
/**
|
|
17
|
+
* Full SIWE flow using a `WalletClient`.
|
|
18
|
+
* - Derives address, fetches nonce, signs message, verifies and stores session.
|
|
19
|
+
*/
|
|
20
|
+
SIWE(wallet: WalletClient, signal?: AbortSignal): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Fetch authenticated user's profile.
|
|
23
|
+
* - Requires valid `session` (Authorization header added automatically).
|
|
24
|
+
*/
|
|
25
|
+
getProfile(signal?: AbortSignal): Promise<UserInfo>;
|
|
26
|
+
/**
|
|
27
|
+
* Determine auth status by checking token presence and profile reachability.
|
|
28
|
+
*/
|
|
29
|
+
getAuthStatus(): Promise<AuthStatus>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Bucket, FileListResponse, GetFilesOptions } from "../types.js";
|
|
2
|
+
import { ModuleBase } from "../base.js";
|
|
3
|
+
export declare class BucketsModule extends ModuleBase {
|
|
4
|
+
/** List all buckets for the current authenticated user */
|
|
5
|
+
listBuckets(signal?: AbortSignal): Promise<Bucket[]>;
|
|
6
|
+
/** Get a specific bucket's metadata by its bucket ID */
|
|
7
|
+
getBucket(bucketId: string, signal?: AbortSignal): Promise<Bucket>;
|
|
8
|
+
/** List files/folders under a path for a bucket (root if no path) */
|
|
9
|
+
getFiles(bucketId: string, options?: GetFilesOptions): Promise<FileListResponse>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ModuleBase } from "../base.js";
|
|
2
|
+
import type { DownloadOptions, DownloadResult, FileInfo, UploadOptions, UploadReceipt } from "../types.js";
|
|
3
|
+
export declare class FilesModule extends ModuleBase {
|
|
4
|
+
/** Get metadata for a file in a bucket by fileKey */
|
|
5
|
+
getFileInfo(bucketId: string, fileKey: string, signal?: AbortSignal): Promise<FileInfo>;
|
|
6
|
+
/** Upload a file to a bucket with a specific key */
|
|
7
|
+
uploadFile(bucketId: string, fileKey: string, file: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | unknown, owner: string, location: string, _options?: UploadOptions): Promise<UploadReceipt>;
|
|
8
|
+
/** Download a file by key */
|
|
9
|
+
downloadFile(fileKey: string, options?: DownloadOptions): Promise<DownloadResult>;
|
|
10
|
+
private coerceToFormPart;
|
|
11
|
+
private computeFileFingerprint;
|
|
12
|
+
private formFileMetadata;
|
|
13
|
+
private hexToBytes;
|
|
14
|
+
private computeFileKey;
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ModuleBase } from "../base.js";
|
|
2
|
+
import type { HealthStatus, InfoResponse, PaymentStreamsResponse, StatsResponse, ValueProp } from "../types.js";
|
|
3
|
+
export declare class InfoModule extends ModuleBase {
|
|
4
|
+
getHealth(signal?: AbortSignal): Promise<HealthStatus>;
|
|
5
|
+
/** Get general MSP information */
|
|
6
|
+
getInfo(signal?: AbortSignal): Promise<InfoResponse>;
|
|
7
|
+
/** Get MSP statistics */
|
|
8
|
+
getStats(signal?: AbortSignal): Promise<StatsResponse>;
|
|
9
|
+
/** Get available value propositions */
|
|
10
|
+
getValuePropositions(signal?: AbortSignal): Promise<ValueProp[]>;
|
|
11
|
+
/** Get payment streams for current authenticated user */
|
|
12
|
+
getPaymentStreams(signal?: AbortSignal): Promise<PaymentStreamsResponse>;
|
|
13
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export interface UploadProgress {
|
|
|
33
33
|
speed: number;
|
|
34
34
|
eta: number;
|
|
35
35
|
}
|
|
36
|
-
export type UploadState =
|
|
36
|
+
export type UploadState = "staged" | "committed";
|
|
37
37
|
export interface UploadOptions {
|
|
38
38
|
bucketId?: Hash;
|
|
39
39
|
replicationFactor?: number;
|
|
@@ -57,9 +57,21 @@ export interface UploadReceipt {
|
|
|
57
57
|
}
|
|
58
58
|
export interface NonceResponse {
|
|
59
59
|
message: string;
|
|
60
|
-
nonce: string;
|
|
61
60
|
}
|
|
62
|
-
export
|
|
61
|
+
export declare enum AuthState {
|
|
62
|
+
NotAuthenticated = "NotAuthenticated",
|
|
63
|
+
TokenExpired = "TokenExpired",
|
|
64
|
+
Authenticated = "Authenticated"
|
|
65
|
+
}
|
|
66
|
+
export interface AuthStatus {
|
|
67
|
+
status: AuthState;
|
|
68
|
+
[k: string]: unknown;
|
|
69
|
+
}
|
|
70
|
+
export interface UserInfo {
|
|
71
|
+
address: string;
|
|
72
|
+
ens: string;
|
|
73
|
+
}
|
|
74
|
+
export interface Session {
|
|
63
75
|
token: string;
|
|
64
76
|
user: {
|
|
65
77
|
address: string;
|
|
@@ -90,16 +102,19 @@ export interface Bucket {
|
|
|
90
102
|
valuePropId: string;
|
|
91
103
|
fileCount: number;
|
|
92
104
|
}
|
|
93
|
-
export type
|
|
94
|
-
export interface FileEntry {
|
|
105
|
+
export type FileTree = {
|
|
95
106
|
name: string;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
} & ({
|
|
108
|
+
type: "file";
|
|
109
|
+
sizeBytes: number;
|
|
110
|
+
fileKey: string;
|
|
111
|
+
} | {
|
|
112
|
+
type: "folder";
|
|
113
|
+
children: FileTree[];
|
|
114
|
+
});
|
|
100
115
|
export interface FileListResponse {
|
|
101
116
|
bucketId: string;
|
|
102
|
-
files:
|
|
117
|
+
files: FileTree[];
|
|
103
118
|
}
|
|
104
119
|
export interface GetFilesOptions {
|
|
105
120
|
path?: string;
|
|
@@ -144,3 +159,13 @@ export interface FileInfo {
|
|
|
144
159
|
isPublic: boolean;
|
|
145
160
|
uploadedAt: Date;
|
|
146
161
|
}
|
|
162
|
+
export type PaymentProviderType = "msp" | "bsp";
|
|
163
|
+
export interface PaymentStreamInfo {
|
|
164
|
+
provider: string;
|
|
165
|
+
providerType: PaymentProviderType;
|
|
166
|
+
totalAmountPaid: string;
|
|
167
|
+
costPerTick: string;
|
|
168
|
+
}
|
|
169
|
+
export interface PaymentStreamsResponse {
|
|
170
|
+
streams: PaymentStreamInfo[];
|
|
171
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storagehub-sdk/msp-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
@@ -23,17 +23,12 @@
|
|
|
23
23
|
"README.md"
|
|
24
24
|
],
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@storagehub-sdk/core": ">=0.0.5"
|
|
26
|
+
"@storagehub-sdk/core": ">=0.0.5",
|
|
27
|
+
"viem": ">=2.37.6"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
|
-
"
|
|
30
|
-
"@
|
|
31
|
-
"@typescript-eslint/parser": "^8.37.0",
|
|
32
|
-
"eslint": "^9.31.0",
|
|
33
|
-
"eslint-config-prettier": "^10.1.5",
|
|
34
|
-
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
35
|
-
"prettier": "^3.6.2",
|
|
36
|
-
"@storagehub-sdk/core": "0.1.0"
|
|
30
|
+
"viem": "2.37.6",
|
|
31
|
+
"@storagehub-sdk/core": "0.2.0"
|
|
37
32
|
},
|
|
38
33
|
"engines": {
|
|
39
34
|
"node": ">=22"
|
|
@@ -44,10 +39,10 @@
|
|
|
44
39
|
"clean": "rm -rf dist node_modules",
|
|
45
40
|
"coverage": "vitest run --coverage",
|
|
46
41
|
"dev": "node ./build.js --watch",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"lint": "
|
|
50
|
-
"lint:fix": "
|
|
42
|
+
"fmt": "biome format .",
|
|
43
|
+
"fmt:fix": "biome format . --write",
|
|
44
|
+
"lint": "biome lint .",
|
|
45
|
+
"lint:fix": "biome lint . --write",
|
|
51
46
|
"test": "vitest",
|
|
52
47
|
"typecheck": "tsc --noEmit"
|
|
53
48
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|