@gethashd/bytecave-browser 1.0.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/AGENTS.md +176 -0
- package/README.md +699 -0
- package/dist/__tests__/p2p-protocols.test.d.ts +10 -0
- package/dist/chunk-EEZWRIUI.js +160 -0
- package/dist/chunk-OJEETLZQ.js +7087 -0
- package/dist/client.d.ts +107 -0
- package/dist/contracts/ContentRegistry.d.ts +1 -0
- package/dist/discovery.d.ts +28 -0
- package/dist/index.cjs +7291 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +50 -0
- package/dist/p2p-protocols.d.ts +114 -0
- package/dist/protocol-handler.cjs +185 -0
- package/dist/protocol-handler.d.ts +57 -0
- package/dist/protocol-handler.js +18 -0
- package/dist/provider.d.ts +59 -0
- package/dist/react/components.d.ts +90 -0
- package/dist/react/hooks.d.ts +67 -0
- package/dist/react/index.cjs +6344 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +23 -0
- package/dist/react/useHashdUrl.d.ts +15 -0
- package/dist/types.d.ts +53 -0
- package/package.json +77 -0
- package/src/__tests__/p2p-protocols.test.ts +292 -0
- package/src/client.ts +876 -0
- package/src/contracts/ContentRegistry.ts +6 -0
- package/src/discovery.ts +79 -0
- package/src/index.ts +59 -0
- package/src/p2p-protocols.ts +451 -0
- package/src/protocol-handler.ts +271 -0
- package/src/provider.tsx +275 -0
- package/src/react/components.tsx +177 -0
- package/src/react/hooks.ts +253 -0
- package/src/react/index.ts +9 -0
- package/src/react/useHashdUrl.ts +68 -0
- package/src/types.ts +60 -0
- package/tsconfig.json +19 -0
- package/tsup.config.ts +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
# ByteCave Browser Client
|
|
2
|
+
|
|
3
|
+
Browser-compatible P2P client library for connecting to the ByteCave decentralized storage network via WebRTC and WebSockets.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Fast Node Discovery** - Discover storage nodes in 1-2 seconds via relay peer directory protocol
|
|
8
|
+
- **WebRTC P2P Connections** - Direct browser-to-node connections using WebRTC transport (via libp2p)
|
|
9
|
+
- **Circuit Relay Fallback** - Relay-based P2P connections for NAT traversal when direct WebRTC fails
|
|
10
|
+
- **Pure P2P Architecture** - All communication via libp2p protocols (no HTTP endpoints required)
|
|
11
|
+
- **FloodSub Announcements** - Peer discovery and announcements via pubsub
|
|
12
|
+
- **Contract Integration** - Optional on-chain node registry verification
|
|
13
|
+
- **HashD Protocol** - Custom `hashd://` URL scheme with caching and helpers
|
|
14
|
+
- **React Hooks & Components** - Ready-to-use React integration
|
|
15
|
+
- **TypeScript** - Full type safety
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @gethashd/bytecave-browser
|
|
21
|
+
# or
|
|
22
|
+
yarn add @gethashd/bytecave-browser
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { ByteCaveClient } from '@gethashd/bytecave-browser';
|
|
29
|
+
|
|
30
|
+
// Initialize client
|
|
31
|
+
const client = new ByteCaveClient({
|
|
32
|
+
appId: 'my-app', // Required: Application identifier
|
|
33
|
+
relayPeers: [
|
|
34
|
+
'/dns4/relay.example.com/tcp/4002/ws/p2p/12D3KooW...'
|
|
35
|
+
],
|
|
36
|
+
vaultNodeRegistryAddress: '0x...', // VaultNodeRegistry contract
|
|
37
|
+
contentRegistryAddress: '0x...', // ContentRegistry contract
|
|
38
|
+
rpcUrl: 'https://...', // Ethereum RPC URL
|
|
39
|
+
maxPeers: 10,
|
|
40
|
+
connectionTimeout: 30000
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Start P2P client
|
|
44
|
+
await client.start();
|
|
45
|
+
|
|
46
|
+
// Store data
|
|
47
|
+
const result = await client.store(
|
|
48
|
+
new TextEncoder().encode('Hello ByteCave!'),
|
|
49
|
+
'text/plain'
|
|
50
|
+
);
|
|
51
|
+
console.log('Stored with CID:', result.cid);
|
|
52
|
+
|
|
53
|
+
// Retrieve data
|
|
54
|
+
const retrieved = await client.retrieve(result.cid);
|
|
55
|
+
console.log('Retrieved:', new TextDecoder().decode(retrieved.data));
|
|
56
|
+
|
|
57
|
+
// Stop client
|
|
58
|
+
await client.stop();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
### ByteCaveConfig
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
interface ByteCaveConfig {
|
|
67
|
+
appId: string; // Required: Application identifier for storage authorization
|
|
68
|
+
relayPeers?: string[]; // Relay node multiaddrs for P2P connections
|
|
69
|
+
directNodeAddrs?: string[]; // Direct node multiaddrs (WebRTC without relay)
|
|
70
|
+
vaultNodeRegistryAddress?: string; // Optional: VaultNodeRegistry contract for node verification
|
|
71
|
+
contentRegistryAddress?: string; // Optional: ContentRegistry contract
|
|
72
|
+
rpcUrl?: string; // Optional: Ethereum RPC (required if using contracts)
|
|
73
|
+
maxPeers?: number; // Maximum peer connections (default: 10)
|
|
74
|
+
connectionTimeout?: number; // Connection timeout in ms (default: 30000)
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Relay Peer Configuration
|
|
79
|
+
|
|
80
|
+
**Required**: At least one relay peer multiaddr for P2P discovery.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
relayPeers: [
|
|
84
|
+
// WebSocket multiaddr (required for browsers)
|
|
85
|
+
'/dns4/relay.example.com/tcp/4002/ws/p2p/12D3KooW...',
|
|
86
|
+
// Multiple relays for redundancy (recommended)
|
|
87
|
+
'/dns4/relay2.example.com/tcp/4002/ws/p2p/12D3KooW...'
|
|
88
|
+
]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Getting Relay Multiaddrs:**
|
|
92
|
+
|
|
93
|
+
From relay HTTP info endpoint:
|
|
94
|
+
```bash
|
|
95
|
+
curl http://relay.example.com:9090/info
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Or from relay node logs:
|
|
99
|
+
```bash
|
|
100
|
+
docker-compose logs relay1 | grep "Listening on"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Important**: Use the WebSocket address (contains `/ws/`) for browser clients.
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
### ByteCaveClient
|
|
108
|
+
|
|
109
|
+
#### Constructor
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
new ByteCaveClient(config: ByteCaveConfig)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### Methods
|
|
116
|
+
|
|
117
|
+
**`start(): Promise<void>`**
|
|
118
|
+
|
|
119
|
+
Start the P2P client and connect to relay nodes.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
await client.start();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**`stop(): Promise<void>`**
|
|
126
|
+
|
|
127
|
+
Stop the P2P client and disconnect from all peers.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
await client.stop();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**`store(data: Uint8Array, mimeType?: string, signer?: any): Promise<StoreResult>`**
|
|
134
|
+
|
|
135
|
+
Store data on the network.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const result = await client.store(data, 'text/plain', signer);
|
|
139
|
+
// Returns: { success: true, cid: '...', peerId: '...' }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Note:** `mimeType` is optional and defaults to `'application/octet-stream'`. Use standard MIME types like `'text/plain'`, `'image/jpeg'`, `'application/json'`, etc.
|
|
143
|
+
|
|
144
|
+
**`retrieve(cid: string): Promise<RetrieveResult>`**
|
|
145
|
+
|
|
146
|
+
Retrieve data from the network.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const result = await client.retrieve('bafybei...');
|
|
150
|
+
// Returns: { success: true, data: Uint8Array, peerId: '...' }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**`getPeers(): PeerInfo[]`**
|
|
154
|
+
|
|
155
|
+
Get list of connected peers.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const peers = client.getPeers();
|
|
159
|
+
// Returns: [{ peerId: '...', publicKey: '...', connected: true, ... }]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**`getConnectionState(): ConnectionState`**
|
|
163
|
+
|
|
164
|
+
Get current connection state.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const state = client.getConnectionState();
|
|
168
|
+
// Returns: 'disconnected' | 'connecting' | 'connected' | 'error'
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### Events
|
|
172
|
+
|
|
173
|
+
**`on(event: string, callback: Function): void`**
|
|
174
|
+
|
|
175
|
+
Listen for events.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
client.on('connectionStateChange', (state) => {
|
|
179
|
+
console.log('Connection state:', state);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
client.on('peerConnect', (peer) => {
|
|
183
|
+
console.log('Peer connected:', peer.peerId);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
client.on('peerDisconnect', (peerId) => {
|
|
187
|
+
console.log('Peer disconnected:', peerId);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
client.on('peerAnnounce', (peer) => {
|
|
191
|
+
console.log('Peer announced:', peer.peerId);
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**`off(event: string, callback: Function): void`**
|
|
196
|
+
|
|
197
|
+
Remove event listener.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
client.off('peerConnect', callback);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## HashD Protocol (`hashd://`)
|
|
204
|
+
|
|
205
|
+
Custom URL scheme for loading content from ByteCave network. **Note:** `hashd://` URLs are not automatically resolved by browsers - you must use the provided utilities to parse and fetch content.
|
|
206
|
+
|
|
207
|
+
### URL Format
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
hashd://{cid}
|
|
211
|
+
hashd://{cid}?type=image/png
|
|
212
|
+
hashd://{cid}?type=image/png&decrypt=true
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Core Functions
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import {
|
|
219
|
+
parseHashdUrl,
|
|
220
|
+
createHashdUrl,
|
|
221
|
+
fetchHashdContent,
|
|
222
|
+
prefetchHashdContent,
|
|
223
|
+
clearHashdCache,
|
|
224
|
+
getHashdCacheStats,
|
|
225
|
+
revokeHashdUrl
|
|
226
|
+
} from '@gethashd/bytecave-browser';
|
|
227
|
+
|
|
228
|
+
// Parse hashd:// URL into components
|
|
229
|
+
const parsed = parseHashdUrl('hashd://bafybei...?type=image/png');
|
|
230
|
+
// { protocol: 'hashd:', cid: 'bafybei...', mimeType: 'image/png', raw: '...' }
|
|
231
|
+
|
|
232
|
+
// Create hashd:// URL from CID
|
|
233
|
+
const url = createHashdUrl('bafybei...', { mimeType: 'image/png' });
|
|
234
|
+
// 'hashd://bafybei...?type=image/png'
|
|
235
|
+
|
|
236
|
+
// Fetch content and get blob URL (with automatic caching)
|
|
237
|
+
const result = await fetchHashdContent(url, client);
|
|
238
|
+
// { data: Uint8Array, blobUrl: 'blob:...', mimeType: 'image/png', cached: false }
|
|
239
|
+
|
|
240
|
+
// Prefetch and cache content
|
|
241
|
+
await prefetchHashdContent('hashd://bafybei...', client);
|
|
242
|
+
|
|
243
|
+
// Cache management
|
|
244
|
+
const stats = getHashdCacheStats(); // { size: 5 }
|
|
245
|
+
clearHashdCache(); // Clear all cached blob URLs
|
|
246
|
+
revokeHashdUrl('bafybei...'); // Revoke specific blob URL
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### How It Works
|
|
250
|
+
|
|
251
|
+
1. **Parse** - `parseHashdUrl()` extracts CID and metadata from URL
|
|
252
|
+
2. **Fetch** - `fetchHashdContent()` retrieves data via P2P from ByteCave network
|
|
253
|
+
3. **Cache** - Blob URLs are automatically cached (1 hour TTL)
|
|
254
|
+
4. **Reuse** - Subsequent requests for same CID return cached blob URL
|
|
255
|
+
|
|
256
|
+
### React Hooks
|
|
257
|
+
|
|
258
|
+
For React apps, use hooks instead of manual fetching:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { useHashdUrl } from '@gethashd/bytecave-browser';
|
|
262
|
+
|
|
263
|
+
function ImageDisplay({ cid }) {
|
|
264
|
+
const { blobUrl, loading, error } = useHashdUrl(`hashd://${cid}`);
|
|
265
|
+
|
|
266
|
+
if (loading) return <Spinner />;
|
|
267
|
+
if (error) return <Error message={error} />;
|
|
268
|
+
return <img src={blobUrl} alt="Image" />;
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## React Integration
|
|
273
|
+
|
|
274
|
+
### Provider Setup (Recommended)
|
|
275
|
+
|
|
276
|
+
The easiest way to use ByteCave in React is with the `ByteCaveProvider`:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { ByteCaveProvider } from '@gethashd/bytecave-browser';
|
|
280
|
+
|
|
281
|
+
function App() {
|
|
282
|
+
return (
|
|
283
|
+
<ByteCaveProvider
|
|
284
|
+
vaultNodeRegistryAddress={process.env.REACT_APP_VAULT_REGISTRY}
|
|
285
|
+
rpcUrl={process.env.REACT_APP_RPC_URL}
|
|
286
|
+
relayPeers={process.env.REACT_APP_RELAY_PEERS?.split(',')}
|
|
287
|
+
>
|
|
288
|
+
<YourApp />
|
|
289
|
+
</ByteCaveProvider>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Then use hooks anywhere in your app:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { useByteCaveContext, useHashdUrl } from '@gethashd/bytecave-browser';
|
|
298
|
+
|
|
299
|
+
function ImageGallery() {
|
|
300
|
+
const { store, retrieve, isConnected } = useByteCaveContext();
|
|
301
|
+
|
|
302
|
+
// Display image from hashd:// URL
|
|
303
|
+
const { blobUrl, loading, error } = useHashdUrl('hashd://abc123...');
|
|
304
|
+
|
|
305
|
+
if (loading) return <div>Loading...</div>;
|
|
306
|
+
if (error) return <div>Error: {error}</div>;
|
|
307
|
+
|
|
308
|
+
return <img src={blobUrl} alt="Stored image" />;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function Uploader() {
|
|
312
|
+
const { store, isConnected } = useByteCaveContext();
|
|
313
|
+
|
|
314
|
+
const handleUpload = async (file: File) => {
|
|
315
|
+
const data = new Uint8Array(await file.arrayBuffer());
|
|
316
|
+
const result = await store(data, file.type);
|
|
317
|
+
console.log('Uploaded with CID:', result.cid);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<input
|
|
322
|
+
type="file"
|
|
323
|
+
onChange={(e) => handleUpload(e.target.files[0])}
|
|
324
|
+
disabled={!isConnected}
|
|
325
|
+
/>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Available Hooks
|
|
331
|
+
|
|
332
|
+
**`useByteCaveContext()`** - Access ByteCave client from context
|
|
333
|
+
|
|
334
|
+
Must be used within `ByteCaveProvider`. Returns:
|
|
335
|
+
```typescript
|
|
336
|
+
{
|
|
337
|
+
connectionState: 'disconnected' | 'connecting' | 'connected' | 'error',
|
|
338
|
+
peers: PeerInfo[],
|
|
339
|
+
isConnected: boolean,
|
|
340
|
+
connect: () => Promise<void>,
|
|
341
|
+
disconnect: () => Promise<void>,
|
|
342
|
+
store: (data: Uint8Array, contentType?: string, signer?: any) => Promise<StoreResult>,
|
|
343
|
+
retrieve: (cid: string) => Promise<RetrieveResult>,
|
|
344
|
+
getNodeHealth: (peerId: string) => Promise<NodeHealth | null>,
|
|
345
|
+
error: string | null
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**`useHashdUrl(url)`** - Convert hashd:// URL to blob URL
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
function ImageDisplay({ cid }) {
|
|
353
|
+
const { blobUrl, loading, error } = useHashdUrl(`hashd://${cid}`);
|
|
354
|
+
|
|
355
|
+
if (loading) return <Spinner />;
|
|
356
|
+
if (error) return <Error message={error} />;
|
|
357
|
+
|
|
358
|
+
return <img src={blobUrl} alt="Image" />;
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**`useHashdImage(url, options)`** - Image-specific loader
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
function ProfilePicture({ cid }) {
|
|
366
|
+
const { src, loading, error } = useHashdImage(`hashd://${cid}`, {
|
|
367
|
+
client,
|
|
368
|
+
placeholder: '/default-avatar.png'
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
if (loading) return <Spinner />;
|
|
372
|
+
if (error) return <ErrorIcon />;
|
|
373
|
+
|
|
374
|
+
return <img src={src} alt="Profile" className="w-32 h-32 rounded-full" />;
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**`useHashdContent(url, options)`** - Generic content loader
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
function ContentViewer({ url }) {
|
|
382
|
+
const { blobUrl, loading, error, mimeType } = useHashdContent(url, { client });
|
|
383
|
+
|
|
384
|
+
if (loading) return <Spinner />;
|
|
385
|
+
if (error) return <Error message={error.message} />;
|
|
386
|
+
|
|
387
|
+
if (mimeType?.startsWith('image/')) {
|
|
388
|
+
return <img src={blobUrl} />;
|
|
389
|
+
} else if (mimeType?.startsWith('video/')) {
|
|
390
|
+
return <video src={blobUrl} controls />;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return <a href={blobUrl} download>Download</a>;
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**`useHashdMedia(url, options)`** - Video/audio loader
|
|
398
|
+
|
|
399
|
+
Returns: `{ src, blobUrl, loading, error, cached, refetch }`
|
|
400
|
+
|
|
401
|
+
**`useHashdBatch(urls, options)`** - Batch load multiple URLs
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
function Gallery({ cids }) {
|
|
405
|
+
const urls = cids.map(cid => `hashd://${cid}`);
|
|
406
|
+
const { results, loading, errors } = useHashdBatch(urls, { client });
|
|
407
|
+
|
|
408
|
+
if (loading) return <Spinner />;
|
|
409
|
+
|
|
410
|
+
return (
|
|
411
|
+
<div className="grid grid-cols-3 gap-4">
|
|
412
|
+
{Array.from(results.entries()).map(([url, result]) => (
|
|
413
|
+
<img key={url} src={result.blobUrl} alt="" />
|
|
414
|
+
))}
|
|
415
|
+
</div>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Components
|
|
421
|
+
|
|
422
|
+
Drop-in replacements for standard HTML elements:
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { HashdImage, HashdVideo, HashdAudio } from '@gethashd/bytecave-browser/react';
|
|
426
|
+
|
|
427
|
+
// Image
|
|
428
|
+
<HashdImage
|
|
429
|
+
src="hashd://abc123..."
|
|
430
|
+
client={byteCaveClient}
|
|
431
|
+
alt="Profile picture"
|
|
432
|
+
className="w-32 h-32 rounded-full"
|
|
433
|
+
placeholder="/loading.png"
|
|
434
|
+
loadingComponent={<Spinner />}
|
|
435
|
+
errorComponent={<ErrorIcon />}
|
|
436
|
+
/>
|
|
437
|
+
|
|
438
|
+
// Video
|
|
439
|
+
<HashdVideo
|
|
440
|
+
src="hashd://def456..."
|
|
441
|
+
client={byteCaveClient}
|
|
442
|
+
controls
|
|
443
|
+
className="w-full"
|
|
444
|
+
/>
|
|
445
|
+
|
|
446
|
+
// Audio
|
|
447
|
+
<HashdAudio
|
|
448
|
+
src="hashd://ghi789..."
|
|
449
|
+
client={byteCaveClient}
|
|
450
|
+
controls
|
|
451
|
+
/>
|
|
452
|
+
|
|
453
|
+
// Render prop pattern
|
|
454
|
+
<HashdContent url="hashd://abc123..." client={client}>
|
|
455
|
+
{({ blobUrl, loading, error }) => {
|
|
456
|
+
if (loading) return <Spinner />;
|
|
457
|
+
if (error) return <Error />;
|
|
458
|
+
return <img src={blobUrl} />;
|
|
459
|
+
}}
|
|
460
|
+
</HashdContent>
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
### Complete Example with Provider
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
import { ByteCaveProvider, useByteCaveContext, useHashdUrl } from '@gethashd/bytecave-browser';
|
|
468
|
+
|
|
469
|
+
// Wrap your app with the provider
|
|
470
|
+
function App() {
|
|
471
|
+
return (
|
|
472
|
+
<ByteCaveProvider
|
|
473
|
+
vaultNodeRegistryAddress={process.env.REACT_APP_VAULT_REGISTRY}
|
|
474
|
+
rpcUrl={process.env.REACT_APP_RPC_URL}
|
|
475
|
+
relayPeers={process.env.REACT_APP_RELAY_PEERS?.split(',')}
|
|
476
|
+
>
|
|
477
|
+
<Gallery />
|
|
478
|
+
</ByteCaveProvider>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Use hooks in any component
|
|
483
|
+
function Gallery() {
|
|
484
|
+
const { isConnected, store } = useByteCaveContext();
|
|
485
|
+
const { blobUrl, loading, error } = useHashdUrl(
|
|
486
|
+
'hashd://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi'
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
const handleUpload = async (file: File) => {
|
|
490
|
+
const data = new Uint8Array(await file.arrayBuffer());
|
|
491
|
+
const result = await store(data, file.type);
|
|
492
|
+
console.log('Uploaded:', result.cid);
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
if (!isConnected) return <div>Connecting to ByteCave...</div>;
|
|
496
|
+
|
|
497
|
+
return (
|
|
498
|
+
<div>
|
|
499
|
+
<h1>ByteCave Gallery</h1>
|
|
500
|
+
|
|
501
|
+
{/* Display image */}
|
|
502
|
+
{loading && <div>Loading image...</div>}
|
|
503
|
+
{error && <div>Error: {error}</div>}
|
|
504
|
+
{blobUrl && <img src={blobUrl} alt="Decentralized" className="max-w-md" />}
|
|
505
|
+
|
|
506
|
+
{/* Upload new image */}
|
|
507
|
+
<input type="file" onChange={(e) => handleUpload(e.target.files[0])} />
|
|
508
|
+
</div>
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Complete Example without Provider
|
|
514
|
+
|
|
515
|
+
If you prefer manual client management:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
import { ByteCaveClient } from '@gethashd/bytecave-browser';
|
|
519
|
+
import { HashdImage } from '@gethashd/bytecave-browser';
|
|
520
|
+
import { useState, useEffect } from 'react';
|
|
521
|
+
|
|
522
|
+
function App() {
|
|
523
|
+
const [client, setClient] = useState(null);
|
|
524
|
+
|
|
525
|
+
useEffect(() => {
|
|
526
|
+
const bytecave = new ByteCaveClient({
|
|
527
|
+
relayPeers: process.env.REACT_APP_RELAY_PEERS?.split(','),
|
|
528
|
+
vaultNodeRegistryAddress: process.env.REACT_APP_VAULT_REGISTRY,
|
|
529
|
+
rpcUrl: process.env.REACT_APP_RPC_URL
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
bytecave.start().then(() => setClient(bytecave));
|
|
533
|
+
|
|
534
|
+
return () => bytecave.stop();
|
|
535
|
+
}, []);
|
|
536
|
+
|
|
537
|
+
if (!client) return <div>Connecting to ByteCave...</div>;
|
|
538
|
+
|
|
539
|
+
return (
|
|
540
|
+
<div>
|
|
541
|
+
<h1>ByteCave Gallery</h1>
|
|
542
|
+
<HashdImage
|
|
543
|
+
src="hashd://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
|
|
544
|
+
client={client}
|
|
545
|
+
alt="Decentralized image"
|
|
546
|
+
className="max-w-md"
|
|
547
|
+
/>
|
|
548
|
+
</div>
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
## Environment Variables
|
|
554
|
+
|
|
555
|
+
For React apps, configure relay peers via environment:
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
# .env
|
|
559
|
+
REACT_APP_RELAY_PEERS=/dns4/relay.example.com/tcp/4002/ws/p2p/12D3KooW...
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Then in your app:
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
const relayPeers = process.env.REACT_APP_RELAY_PEERS?.split(',') || [];
|
|
566
|
+
|
|
567
|
+
const client = new ByteCaveClient({
|
|
568
|
+
contractAddress: process.env.REACT_APP_VAULT_REGISTRY,
|
|
569
|
+
rpcUrl: process.env.REACT_APP_RPC_URL,
|
|
570
|
+
relayPeers
|
|
571
|
+
});
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## How It Works
|
|
575
|
+
|
|
576
|
+
### Fast Discovery via Peer Directory
|
|
577
|
+
|
|
578
|
+
On startup, the browser queries the relay's peer directory protocol for instant node discovery:
|
|
579
|
+
|
|
580
|
+
1. **Connect to Relay** - Browser connects to relay via WebSocket
|
|
581
|
+
2. **Query Peer Directory** - Browser dials `/bytecave/relay/peers/1.0.0` protocol
|
|
582
|
+
3. **Receive Peer List** - Relay responds with list of storage nodes and circuit relay addresses
|
|
583
|
+
4. **Dial Nodes** - Browser dials each node through the relay
|
|
584
|
+
5. **Fetch Health Data** - Immediate health check via P2P protocol
|
|
585
|
+
|
|
586
|
+
**Discovery time: 1-2 seconds** (down from 2+ minutes with gossip-only)
|
|
587
|
+
|
|
588
|
+
### Pure P2P Discovery
|
|
589
|
+
|
|
590
|
+
1. **Connect to Relay** - Browser connects to relay via WebSocket
|
|
591
|
+
2. **Announce Presence** - Node announces on FloodSub `bytecave-announce` topic
|
|
592
|
+
3. **Discover Peers** - DHT and pubsub discover other nodes through relay
|
|
593
|
+
4. **Establish Connection** - Direct WebRTC or relayed connection
|
|
594
|
+
5. **Store/Retrieve** - P2P protocols for data operations
|
|
595
|
+
|
|
596
|
+
### Pure P2P Architecture
|
|
597
|
+
|
|
598
|
+
- ✅ No HTTP health endpoint calls
|
|
599
|
+
- ✅ No HTTP multiaddr fetching
|
|
600
|
+
- ✅ Pure libp2p protocols (Peer Directory, FloodSub, DHT)
|
|
601
|
+
- ✅ Works entirely over P2P network
|
|
602
|
+
- ✅ Browser connects directly to storage nodes via WebRTC or relay
|
|
603
|
+
|
|
604
|
+
## Protocols
|
|
605
|
+
|
|
606
|
+
- **Peer Directory** (`/bytecave/relay/peers/1.0.0`) - Fast node discovery
|
|
607
|
+
- **Health Protocol** (`/bytecave/health/1.0.0`) - Node health checks
|
|
608
|
+
- **WebSocket** - Browser to relay connection
|
|
609
|
+
- **WebRTC** - Direct browser-to-node connections
|
|
610
|
+
- **Circuit Relay v2** - NAT traversal
|
|
611
|
+
- **FloodSub** - Peer announcements
|
|
612
|
+
- **DHT** (via relay) - Distributed peer routing
|
|
613
|
+
|
|
614
|
+
## Browser Compatibility
|
|
615
|
+
|
|
616
|
+
- Chrome/Edge 89+
|
|
617
|
+
- Firefox 87+
|
|
618
|
+
- Safari 15.4+
|
|
619
|
+
- Opera 75+
|
|
620
|
+
|
|
621
|
+
Requires WebRTC and WebSocket support.
|
|
622
|
+
|
|
623
|
+
## Troubleshooting
|
|
624
|
+
|
|
625
|
+
### No Peers Discovered
|
|
626
|
+
|
|
627
|
+
**Check relay configuration:**
|
|
628
|
+
```typescript
|
|
629
|
+
console.log('Relay peers:', config.relayPeers);
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**Verify relay is running:**
|
|
633
|
+
```bash
|
|
634
|
+
curl http://relay.example.com:4002
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
**Check browser console:**
|
|
638
|
+
```
|
|
639
|
+
[ByteCave] Using relay peers: [...]
|
|
640
|
+
[ByteCave] ✓ Connected to relay: ...
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Connection Failed
|
|
644
|
+
|
|
645
|
+
1. Verify relay multiaddr format includes `/ws/`
|
|
646
|
+
2. Check firewall allows WebSocket connections
|
|
647
|
+
3. Ensure relay peer ID matches running relay
|
|
648
|
+
4. Try connecting to relay directly in browser DevTools
|
|
649
|
+
|
|
650
|
+
### CORS Issues
|
|
651
|
+
|
|
652
|
+
WebSocket connections don't have CORS restrictions. If you see CORS errors, you may be using HTTP instead of WS.
|
|
653
|
+
|
|
654
|
+
## Development
|
|
655
|
+
|
|
656
|
+
```bash
|
|
657
|
+
# Install dependencies
|
|
658
|
+
yarn install
|
|
659
|
+
|
|
660
|
+
# Build
|
|
661
|
+
yarn build
|
|
662
|
+
|
|
663
|
+
# Run tests
|
|
664
|
+
yarn test
|
|
665
|
+
|
|
666
|
+
# Watch mode
|
|
667
|
+
yarn dev
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## TypeScript Support
|
|
671
|
+
|
|
672
|
+
Full TypeScript definitions included:
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
import type {
|
|
676
|
+
ByteCaveConfig,
|
|
677
|
+
PeerInfo,
|
|
678
|
+
StoreResult,
|
|
679
|
+
RetrieveResult,
|
|
680
|
+
ConnectionState,
|
|
681
|
+
HashdUrl,
|
|
682
|
+
FetchOptions,
|
|
683
|
+
FetchResult
|
|
684
|
+
} from '@gethashd/bytecave-browser';
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
## License
|
|
688
|
+
|
|
689
|
+
MIT
|
|
690
|
+
|
|
691
|
+
## Related Packages
|
|
692
|
+
|
|
693
|
+
- **bytecave-core** - Storage node implementation
|
|
694
|
+
- **bytecave-relay** - Relay node for NAT traversal
|
|
695
|
+
- **bytecave-desktop** - Desktop application
|
|
696
|
+
|
|
697
|
+
## Support
|
|
698
|
+
|
|
699
|
+
For issues and questions, please open an issue on GitHub.
|