@gethashd/bytecave-browser 1.0.47 → 1.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-FOQ3CE3H.js → chunk-CVBYHRQD.js} +225 -23
- package/dist/client.d.ts +9 -0
- package/dist/index.cjs +225 -23
- package/dist/index.js +1 -1
- package/dist/react/index.js +1 -1
- package/dist/storage-webtransport.d.ts +36 -0
- package/package.json +2 -1
- package/src/client.ts +154 -24
- package/src/storage-webtransport.ts +156 -0
|
@@ -6226,6 +6226,101 @@ var StorageWebSocketClient = class {
|
|
|
6226
6226
|
}
|
|
6227
6227
|
};
|
|
6228
6228
|
|
|
6229
|
+
// src/storage-webtransport.ts
|
|
6230
|
+
var StorageWebTransportClient = class {
|
|
6231
|
+
constructor(nodeMultiaddr) {
|
|
6232
|
+
this.nodeMultiaddr = nodeMultiaddr;
|
|
6233
|
+
}
|
|
6234
|
+
/**
|
|
6235
|
+
* Store data via WebTransport direct to node
|
|
6236
|
+
*/
|
|
6237
|
+
async store(request) {
|
|
6238
|
+
try {
|
|
6239
|
+
if (typeof WebTransport === "undefined") {
|
|
6240
|
+
return {
|
|
6241
|
+
success: false,
|
|
6242
|
+
error: "WebTransport not supported in this browser"
|
|
6243
|
+
};
|
|
6244
|
+
}
|
|
6245
|
+
const url = this.multiaddrToWebTransportUrl(this.nodeMultiaddr);
|
|
6246
|
+
if (!url) {
|
|
6247
|
+
return {
|
|
6248
|
+
success: false,
|
|
6249
|
+
error: "Invalid WebTransport multiaddr"
|
|
6250
|
+
};
|
|
6251
|
+
}
|
|
6252
|
+
console.log("[WebTransport] Connecting to node:", url);
|
|
6253
|
+
const transport = new WebTransport(url);
|
|
6254
|
+
await transport.ready;
|
|
6255
|
+
console.log("[WebTransport] Connected, opening bidirectional stream");
|
|
6256
|
+
const stream = await transport.createBidirectionalStream();
|
|
6257
|
+
const writer = stream.writable.getWriter();
|
|
6258
|
+
const reader = stream.readable.getReader();
|
|
6259
|
+
const requestData = {
|
|
6260
|
+
type: "storage-request",
|
|
6261
|
+
data: Array.from(request.data),
|
|
6262
|
+
// Convert Uint8Array to array for JSON
|
|
6263
|
+
contentType: request.contentType,
|
|
6264
|
+
hashIdToken: request.hashIdToken,
|
|
6265
|
+
authorization: request.authorization
|
|
6266
|
+
};
|
|
6267
|
+
const requestJson = JSON.stringify(requestData);
|
|
6268
|
+
const requestBytes = new TextEncoder().encode(requestJson);
|
|
6269
|
+
await writer.write(requestBytes);
|
|
6270
|
+
await writer.close();
|
|
6271
|
+
console.log("[WebTransport] Request sent, waiting for response");
|
|
6272
|
+
const { value, done } = await reader.read();
|
|
6273
|
+
if (done || !value) {
|
|
6274
|
+
return {
|
|
6275
|
+
success: false,
|
|
6276
|
+
error: "No response from node"
|
|
6277
|
+
};
|
|
6278
|
+
}
|
|
6279
|
+
const responseJson = new TextDecoder().decode(value);
|
|
6280
|
+
const response = JSON.parse(responseJson);
|
|
6281
|
+
console.log("[WebTransport] Response received:", response);
|
|
6282
|
+
await transport.close();
|
|
6283
|
+
return response;
|
|
6284
|
+
} catch (error) {
|
|
6285
|
+
console.error("[WebTransport] Storage failed:", error);
|
|
6286
|
+
return {
|
|
6287
|
+
success: false,
|
|
6288
|
+
error: error.message || "WebTransport storage failed"
|
|
6289
|
+
};
|
|
6290
|
+
}
|
|
6291
|
+
}
|
|
6292
|
+
/**
|
|
6293
|
+
* Convert libp2p multiaddr to WebTransport URL
|
|
6294
|
+
*/
|
|
6295
|
+
multiaddrToWebTransportUrl(multiaddr2) {
|
|
6296
|
+
try {
|
|
6297
|
+
const parts = multiaddr2.split("/").filter((p) => p);
|
|
6298
|
+
let ip = "";
|
|
6299
|
+
let port = "";
|
|
6300
|
+
let hasQuic = false;
|
|
6301
|
+
let hasWebTransport = false;
|
|
6302
|
+
for (let i = 0; i < parts.length; i++) {
|
|
6303
|
+
if (parts[i] === "ip4" && i + 1 < parts.length) {
|
|
6304
|
+
ip = parts[i + 1];
|
|
6305
|
+
} else if (parts[i] === "udp" && i + 1 < parts.length) {
|
|
6306
|
+
port = parts[i + 1];
|
|
6307
|
+
} else if (parts[i] === "quic-v1") {
|
|
6308
|
+
hasQuic = true;
|
|
6309
|
+
} else if (parts[i] === "webtransport") {
|
|
6310
|
+
hasWebTransport = true;
|
|
6311
|
+
}
|
|
6312
|
+
}
|
|
6313
|
+
if (!ip || !port || !hasQuic || !hasWebTransport) {
|
|
6314
|
+
return null;
|
|
6315
|
+
}
|
|
6316
|
+
return `https://${ip}:${port}`;
|
|
6317
|
+
} catch (error) {
|
|
6318
|
+
console.error("[WebTransport] Failed to parse multiaddr:", error);
|
|
6319
|
+
return null;
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
};
|
|
6323
|
+
|
|
6229
6324
|
// src/contracts/ContentRegistry.ts
|
|
6230
6325
|
var CONTENT_REGISTRY_ABI = [
|
|
6231
6326
|
"function registerContent(bytes32 cid, address owner, bytes32 appId, uint256 hashIdToken) external",
|
|
@@ -6242,6 +6337,7 @@ var ByteCaveClient = class {
|
|
|
6242
6337
|
this.knownPeers = /* @__PURE__ */ new Map();
|
|
6243
6338
|
this.connectionState = "disconnected";
|
|
6244
6339
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
6340
|
+
this.PEERS_CACHE_KEY = "bytecave_known_peers";
|
|
6245
6341
|
this.config = {
|
|
6246
6342
|
maxPeers: 10,
|
|
6247
6343
|
connectionTimeout: 3e4,
|
|
@@ -6254,6 +6350,7 @@ var ByteCaveClient = class {
|
|
|
6254
6350
|
if (config.vaultNodeRegistryAddress && config.rpcUrl) {
|
|
6255
6351
|
this.contractDiscovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
|
|
6256
6352
|
}
|
|
6353
|
+
this.loadCachedPeers();
|
|
6257
6354
|
}
|
|
6258
6355
|
/**
|
|
6259
6356
|
* Initialize and start the P2P client
|
|
@@ -6423,14 +6520,19 @@ var ByteCaveClient = class {
|
|
|
6423
6520
|
}
|
|
6424
6521
|
const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
|
|
6425
6522
|
if (health) {
|
|
6426
|
-
|
|
6523
|
+
const peerInfo = {
|
|
6427
6524
|
peerId: peer.peerId,
|
|
6428
6525
|
publicKey: health.publicKey || "",
|
|
6429
6526
|
contentTypes: health.contentTypes || "all",
|
|
6527
|
+
httpEndpoint: health.httpEndpoint || "",
|
|
6528
|
+
multiaddrs: peer.multiaddrs || [],
|
|
6529
|
+
relayAddrs: peer.relayAddrs || [],
|
|
6430
6530
|
connected: true,
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6531
|
+
isRegistered: peer.isRegistered || false,
|
|
6532
|
+
onChainNodeId: peer.onChainNodeId || ""
|
|
6533
|
+
};
|
|
6534
|
+
this.knownPeers.set(peer.peerId, peerInfo);
|
|
6535
|
+
this.saveCachedPeers();
|
|
6434
6536
|
}
|
|
6435
6537
|
} catch (err) {
|
|
6436
6538
|
console.warn("[ByteCave] Failed to process peer from directory:", peer.peerId.slice(0, 12), err.message);
|
|
@@ -6519,14 +6621,19 @@ var ByteCaveClient = class {
|
|
|
6519
6621
|
try {
|
|
6520
6622
|
const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
|
|
6521
6623
|
if (health) {
|
|
6522
|
-
|
|
6624
|
+
const peerInfo = {
|
|
6523
6625
|
peerId: peer.peerId,
|
|
6524
6626
|
publicKey: health.publicKey || "",
|
|
6525
6627
|
contentTypes: health.contentTypes || "all",
|
|
6628
|
+
httpEndpoint: health.httpEndpoint || "",
|
|
6629
|
+
multiaddrs: peer.multiaddrs || [],
|
|
6630
|
+
relayAddrs: peer.relayAddrs || [],
|
|
6526
6631
|
connected: true,
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6632
|
+
isRegistered: peer.isRegistered || false,
|
|
6633
|
+
onChainNodeId: peer.onChainNodeId || ""
|
|
6634
|
+
};
|
|
6635
|
+
this.knownPeers.set(peer.peerId, peerInfo);
|
|
6636
|
+
this.saveCachedPeers();
|
|
6530
6637
|
}
|
|
6531
6638
|
} catch (err) {
|
|
6532
6639
|
console.warn("[ByteCave] Refresh: Failed to get health from peer:", peer.peerId.slice(0, 12), err.message);
|
|
@@ -6591,15 +6698,81 @@ Nonce: ${nonce}`;
|
|
|
6591
6698
|
console.warn("[ByteCave] Failed to create authorization:", err.message);
|
|
6592
6699
|
}
|
|
6593
6700
|
}
|
|
6594
|
-
if (
|
|
6595
|
-
|
|
6701
|
+
if (this.config.relayWsUrl) {
|
|
6702
|
+
console.log("[ByteCave] Attempting storage via WebSocket relay");
|
|
6703
|
+
try {
|
|
6704
|
+
if (!this.storageWsClient) {
|
|
6705
|
+
this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
|
|
6706
|
+
}
|
|
6707
|
+
const wsAuth = authorization ? {
|
|
6708
|
+
signature: authorization.signature,
|
|
6709
|
+
address: authorization.sender,
|
|
6710
|
+
timestamp: authorization.timestamp,
|
|
6711
|
+
nonce: authorization.nonce,
|
|
6712
|
+
appId: authorization.appId,
|
|
6713
|
+
contentHash: authorization.contentHash
|
|
6714
|
+
} : void 0;
|
|
6715
|
+
const result = await this.storageWsClient.store({
|
|
6716
|
+
data: dataArray,
|
|
6717
|
+
contentType: mimeType || "application/octet-stream",
|
|
6718
|
+
hashIdToken,
|
|
6719
|
+
authorization: wsAuth,
|
|
6720
|
+
timeout: 3e4
|
|
6721
|
+
});
|
|
6722
|
+
if (result.success && result.cid) {
|
|
6723
|
+
console.log("[ByteCave] \u2713 WebSocket storage successful:", result.cid);
|
|
6724
|
+
return {
|
|
6725
|
+
success: true,
|
|
6726
|
+
cid: result.cid,
|
|
6727
|
+
peerId: "relay-ws"
|
|
6728
|
+
};
|
|
6729
|
+
}
|
|
6730
|
+
console.warn("[ByteCave] WebSocket storage failed, trying WebTransport:", result.error);
|
|
6731
|
+
} catch (err) {
|
|
6732
|
+
console.warn("[ByteCave] WebSocket storage exception, trying WebTransport:", err.message);
|
|
6733
|
+
}
|
|
6734
|
+
} else {
|
|
6735
|
+
console.log("[ByteCave] WebSocket relay not configured, trying WebTransport");
|
|
6596
6736
|
}
|
|
6597
|
-
console.log("[ByteCave]
|
|
6737
|
+
console.log("[ByteCave] Attempting storage via WebTransport direct");
|
|
6598
6738
|
try {
|
|
6599
|
-
|
|
6600
|
-
|
|
6739
|
+
let nodes = Array.from(this.knownPeers.values());
|
|
6740
|
+
if (nodes.length === 0) {
|
|
6741
|
+
console.log("[ByteCave] No cached peers, trying discovery services");
|
|
6742
|
+
if (this.relayDiscovery) {
|
|
6743
|
+
nodes = await this.relayDiscovery.getConnectedPeers();
|
|
6744
|
+
} else if (this.contractDiscovery) {
|
|
6745
|
+
nodes = await this.contractDiscovery.getActiveNodes();
|
|
6746
|
+
}
|
|
6747
|
+
} else {
|
|
6748
|
+
console.log("[ByteCave] Using", nodes.length, "cached peers for WebTransport");
|
|
6601
6749
|
}
|
|
6602
|
-
|
|
6750
|
+
if (nodes.length === 0) {
|
|
6751
|
+
return {
|
|
6752
|
+
success: false,
|
|
6753
|
+
error: "No nodes available for WebTransport storage"
|
|
6754
|
+
};
|
|
6755
|
+
}
|
|
6756
|
+
const nodeWithWebTransport = nodes.find(
|
|
6757
|
+
(node) => node.multiaddrs?.some((addr) => addr.includes("/webtransport"))
|
|
6758
|
+
);
|
|
6759
|
+
if (!nodeWithWebTransport || !nodeWithWebTransport.multiaddrs) {
|
|
6760
|
+
return {
|
|
6761
|
+
success: false,
|
|
6762
|
+
error: "No nodes with WebTransport support found"
|
|
6763
|
+
};
|
|
6764
|
+
}
|
|
6765
|
+
const wtMultiaddr = nodeWithWebTransport.multiaddrs.find(
|
|
6766
|
+
(addr) => addr.includes("/webtransport")
|
|
6767
|
+
);
|
|
6768
|
+
if (!wtMultiaddr) {
|
|
6769
|
+
return {
|
|
6770
|
+
success: false,
|
|
6771
|
+
error: "No WebTransport multiaddr found"
|
|
6772
|
+
};
|
|
6773
|
+
}
|
|
6774
|
+
const wtClient = new StorageWebTransportClient(wtMultiaddr);
|
|
6775
|
+
const wtAuth = authorization ? {
|
|
6603
6776
|
signature: authorization.signature,
|
|
6604
6777
|
address: authorization.sender,
|
|
6605
6778
|
timestamp: authorization.timestamp,
|
|
@@ -6607,26 +6780,25 @@ Nonce: ${nonce}`;
|
|
|
6607
6780
|
appId: authorization.appId,
|
|
6608
6781
|
contentHash: authorization.contentHash
|
|
6609
6782
|
} : void 0;
|
|
6610
|
-
const result = await
|
|
6783
|
+
const result = await wtClient.store({
|
|
6611
6784
|
data: dataArray,
|
|
6612
6785
|
contentType: mimeType || "application/octet-stream",
|
|
6613
6786
|
hashIdToken,
|
|
6614
|
-
authorization:
|
|
6615
|
-
timeout: 3e4
|
|
6787
|
+
authorization: wtAuth
|
|
6616
6788
|
});
|
|
6617
6789
|
if (result.success && result.cid) {
|
|
6618
|
-
console.log("[ByteCave] \u2713
|
|
6790
|
+
console.log("[ByteCave] \u2713 WebTransport storage successful:", result.cid);
|
|
6619
6791
|
return {
|
|
6620
6792
|
success: true,
|
|
6621
6793
|
cid: result.cid,
|
|
6622
|
-
peerId:
|
|
6794
|
+
peerId: nodeWithWebTransport.peerId
|
|
6623
6795
|
};
|
|
6624
6796
|
}
|
|
6625
|
-
console.
|
|
6626
|
-
return { success: false, error: result.error || "
|
|
6797
|
+
console.error("[ByteCave] WebTransport storage failed:", result.error);
|
|
6798
|
+
return { success: false, error: result.error || "All storage methods failed" };
|
|
6627
6799
|
} catch (err) {
|
|
6628
|
-
console.error("[ByteCave]
|
|
6629
|
-
return { success: false, error: err.message };
|
|
6800
|
+
console.error("[ByteCave] WebTransport storage exception:", err.message);
|
|
6801
|
+
return { success: false, error: `All storage methods failed: ${err.message}` };
|
|
6630
6802
|
}
|
|
6631
6803
|
}
|
|
6632
6804
|
/**
|
|
@@ -6891,8 +7063,38 @@ Nonce: ${nonce}`;
|
|
|
6891
7063
|
onChainNodeId: announcement.onChainNodeId
|
|
6892
7064
|
};
|
|
6893
7065
|
this.knownPeers.set(announcement.peerId, peerInfo);
|
|
7066
|
+
this.saveCachedPeers();
|
|
6894
7067
|
this.emit("peerAnnounce", peerInfo);
|
|
6895
7068
|
}
|
|
7069
|
+
/**
|
|
7070
|
+
* Load cached peers from localStorage
|
|
7071
|
+
*/
|
|
7072
|
+
loadCachedPeers() {
|
|
7073
|
+
try {
|
|
7074
|
+
const cached = localStorage.getItem(this.PEERS_CACHE_KEY);
|
|
7075
|
+
if (cached) {
|
|
7076
|
+
const peers = JSON.parse(cached);
|
|
7077
|
+
console.log("[ByteCave] Loaded", peers.length, "cached peers from localStorage");
|
|
7078
|
+
for (const peer of peers) {
|
|
7079
|
+
this.knownPeers.set(peer.peerId, { ...peer, connected: false });
|
|
7080
|
+
}
|
|
7081
|
+
}
|
|
7082
|
+
} catch (error) {
|
|
7083
|
+
console.warn("[ByteCave] Failed to load cached peers:", error);
|
|
7084
|
+
}
|
|
7085
|
+
}
|
|
7086
|
+
/**
|
|
7087
|
+
* Save known peers to localStorage
|
|
7088
|
+
*/
|
|
7089
|
+
saveCachedPeers() {
|
|
7090
|
+
try {
|
|
7091
|
+
const peers = Array.from(this.knownPeers.values());
|
|
7092
|
+
localStorage.setItem(this.PEERS_CACHE_KEY, JSON.stringify(peers));
|
|
7093
|
+
console.log("[ByteCave] Saved", peers.length, "peers to localStorage");
|
|
7094
|
+
} catch (error) {
|
|
7095
|
+
console.warn("[ByteCave] Failed to save cached peers:", error);
|
|
7096
|
+
}
|
|
7097
|
+
}
|
|
6896
7098
|
/**
|
|
6897
7099
|
* Check if a nodeId is registered in the on-chain registry
|
|
6898
7100
|
*/
|
package/dist/client.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare class ByteCaveClient {
|
|
|
14
14
|
private knownPeers;
|
|
15
15
|
private connectionState;
|
|
16
16
|
private eventListeners;
|
|
17
|
+
private readonly PEERS_CACHE_KEY;
|
|
17
18
|
constructor(config: ByteCaveConfig);
|
|
18
19
|
/**
|
|
19
20
|
* Initialize and start the P2P client
|
|
@@ -97,6 +98,14 @@ export declare class ByteCaveClient {
|
|
|
97
98
|
private setupEventListeners;
|
|
98
99
|
private setupPubsub;
|
|
99
100
|
private handlePeerAnnouncement;
|
|
101
|
+
/**
|
|
102
|
+
* Load cached peers from localStorage
|
|
103
|
+
*/
|
|
104
|
+
private loadCachedPeers;
|
|
105
|
+
/**
|
|
106
|
+
* Save known peers to localStorage
|
|
107
|
+
*/
|
|
108
|
+
private saveCachedPeers;
|
|
100
109
|
/**
|
|
101
110
|
* Check if a nodeId is registered in the on-chain registry
|
|
102
111
|
*/
|
package/dist/index.cjs
CHANGED
|
@@ -6279,6 +6279,101 @@ var StorageWebSocketClient = class {
|
|
|
6279
6279
|
}
|
|
6280
6280
|
};
|
|
6281
6281
|
|
|
6282
|
+
// src/storage-webtransport.ts
|
|
6283
|
+
var StorageWebTransportClient = class {
|
|
6284
|
+
constructor(nodeMultiaddr) {
|
|
6285
|
+
this.nodeMultiaddr = nodeMultiaddr;
|
|
6286
|
+
}
|
|
6287
|
+
/**
|
|
6288
|
+
* Store data via WebTransport direct to node
|
|
6289
|
+
*/
|
|
6290
|
+
async store(request) {
|
|
6291
|
+
try {
|
|
6292
|
+
if (typeof WebTransport === "undefined") {
|
|
6293
|
+
return {
|
|
6294
|
+
success: false,
|
|
6295
|
+
error: "WebTransport not supported in this browser"
|
|
6296
|
+
};
|
|
6297
|
+
}
|
|
6298
|
+
const url = this.multiaddrToWebTransportUrl(this.nodeMultiaddr);
|
|
6299
|
+
if (!url) {
|
|
6300
|
+
return {
|
|
6301
|
+
success: false,
|
|
6302
|
+
error: "Invalid WebTransport multiaddr"
|
|
6303
|
+
};
|
|
6304
|
+
}
|
|
6305
|
+
console.log("[WebTransport] Connecting to node:", url);
|
|
6306
|
+
const transport = new WebTransport(url);
|
|
6307
|
+
await transport.ready;
|
|
6308
|
+
console.log("[WebTransport] Connected, opening bidirectional stream");
|
|
6309
|
+
const stream = await transport.createBidirectionalStream();
|
|
6310
|
+
const writer = stream.writable.getWriter();
|
|
6311
|
+
const reader = stream.readable.getReader();
|
|
6312
|
+
const requestData = {
|
|
6313
|
+
type: "storage-request",
|
|
6314
|
+
data: Array.from(request.data),
|
|
6315
|
+
// Convert Uint8Array to array for JSON
|
|
6316
|
+
contentType: request.contentType,
|
|
6317
|
+
hashIdToken: request.hashIdToken,
|
|
6318
|
+
authorization: request.authorization
|
|
6319
|
+
};
|
|
6320
|
+
const requestJson = JSON.stringify(requestData);
|
|
6321
|
+
const requestBytes = new TextEncoder().encode(requestJson);
|
|
6322
|
+
await writer.write(requestBytes);
|
|
6323
|
+
await writer.close();
|
|
6324
|
+
console.log("[WebTransport] Request sent, waiting for response");
|
|
6325
|
+
const { value, done } = await reader.read();
|
|
6326
|
+
if (done || !value) {
|
|
6327
|
+
return {
|
|
6328
|
+
success: false,
|
|
6329
|
+
error: "No response from node"
|
|
6330
|
+
};
|
|
6331
|
+
}
|
|
6332
|
+
const responseJson = new TextDecoder().decode(value);
|
|
6333
|
+
const response = JSON.parse(responseJson);
|
|
6334
|
+
console.log("[WebTransport] Response received:", response);
|
|
6335
|
+
await transport.close();
|
|
6336
|
+
return response;
|
|
6337
|
+
} catch (error) {
|
|
6338
|
+
console.error("[WebTransport] Storage failed:", error);
|
|
6339
|
+
return {
|
|
6340
|
+
success: false,
|
|
6341
|
+
error: error.message || "WebTransport storage failed"
|
|
6342
|
+
};
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
/**
|
|
6346
|
+
* Convert libp2p multiaddr to WebTransport URL
|
|
6347
|
+
*/
|
|
6348
|
+
multiaddrToWebTransportUrl(multiaddr2) {
|
|
6349
|
+
try {
|
|
6350
|
+
const parts = multiaddr2.split("/").filter((p) => p);
|
|
6351
|
+
let ip = "";
|
|
6352
|
+
let port = "";
|
|
6353
|
+
let hasQuic = false;
|
|
6354
|
+
let hasWebTransport = false;
|
|
6355
|
+
for (let i = 0; i < parts.length; i++) {
|
|
6356
|
+
if (parts[i] === "ip4" && i + 1 < parts.length) {
|
|
6357
|
+
ip = parts[i + 1];
|
|
6358
|
+
} else if (parts[i] === "udp" && i + 1 < parts.length) {
|
|
6359
|
+
port = parts[i + 1];
|
|
6360
|
+
} else if (parts[i] === "quic-v1") {
|
|
6361
|
+
hasQuic = true;
|
|
6362
|
+
} else if (parts[i] === "webtransport") {
|
|
6363
|
+
hasWebTransport = true;
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
if (!ip || !port || !hasQuic || !hasWebTransport) {
|
|
6367
|
+
return null;
|
|
6368
|
+
}
|
|
6369
|
+
return `https://${ip}:${port}`;
|
|
6370
|
+
} catch (error) {
|
|
6371
|
+
console.error("[WebTransport] Failed to parse multiaddr:", error);
|
|
6372
|
+
return null;
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
};
|
|
6376
|
+
|
|
6282
6377
|
// src/contracts/ContentRegistry.ts
|
|
6283
6378
|
var CONTENT_REGISTRY_ABI = [
|
|
6284
6379
|
"function registerContent(bytes32 cid, address owner, bytes32 appId, uint256 hashIdToken) external",
|
|
@@ -6295,6 +6390,7 @@ var ByteCaveClient = class {
|
|
|
6295
6390
|
this.knownPeers = /* @__PURE__ */ new Map();
|
|
6296
6391
|
this.connectionState = "disconnected";
|
|
6297
6392
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
6393
|
+
this.PEERS_CACHE_KEY = "bytecave_known_peers";
|
|
6298
6394
|
this.config = {
|
|
6299
6395
|
maxPeers: 10,
|
|
6300
6396
|
connectionTimeout: 3e4,
|
|
@@ -6307,6 +6403,7 @@ var ByteCaveClient = class {
|
|
|
6307
6403
|
if (config.vaultNodeRegistryAddress && config.rpcUrl) {
|
|
6308
6404
|
this.contractDiscovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
|
|
6309
6405
|
}
|
|
6406
|
+
this.loadCachedPeers();
|
|
6310
6407
|
}
|
|
6311
6408
|
/**
|
|
6312
6409
|
* Initialize and start the P2P client
|
|
@@ -6476,14 +6573,19 @@ var ByteCaveClient = class {
|
|
|
6476
6573
|
}
|
|
6477
6574
|
const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
|
|
6478
6575
|
if (health) {
|
|
6479
|
-
|
|
6576
|
+
const peerInfo = {
|
|
6480
6577
|
peerId: peer.peerId,
|
|
6481
6578
|
publicKey: health.publicKey || "",
|
|
6482
6579
|
contentTypes: health.contentTypes || "all",
|
|
6580
|
+
httpEndpoint: health.httpEndpoint || "",
|
|
6581
|
+
multiaddrs: peer.multiaddrs || [],
|
|
6582
|
+
relayAddrs: peer.relayAddrs || [],
|
|
6483
6583
|
connected: true,
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6584
|
+
isRegistered: peer.isRegistered || false,
|
|
6585
|
+
onChainNodeId: peer.onChainNodeId || ""
|
|
6586
|
+
};
|
|
6587
|
+
this.knownPeers.set(peer.peerId, peerInfo);
|
|
6588
|
+
this.saveCachedPeers();
|
|
6487
6589
|
}
|
|
6488
6590
|
} catch (err) {
|
|
6489
6591
|
console.warn("[ByteCave] Failed to process peer from directory:", peer.peerId.slice(0, 12), err.message);
|
|
@@ -6572,14 +6674,19 @@ var ByteCaveClient = class {
|
|
|
6572
6674
|
try {
|
|
6573
6675
|
const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
|
|
6574
6676
|
if (health) {
|
|
6575
|
-
|
|
6677
|
+
const peerInfo = {
|
|
6576
6678
|
peerId: peer.peerId,
|
|
6577
6679
|
publicKey: health.publicKey || "",
|
|
6578
6680
|
contentTypes: health.contentTypes || "all",
|
|
6681
|
+
httpEndpoint: health.httpEndpoint || "",
|
|
6682
|
+
multiaddrs: peer.multiaddrs || [],
|
|
6683
|
+
relayAddrs: peer.relayAddrs || [],
|
|
6579
6684
|
connected: true,
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6685
|
+
isRegistered: peer.isRegistered || false,
|
|
6686
|
+
onChainNodeId: peer.onChainNodeId || ""
|
|
6687
|
+
};
|
|
6688
|
+
this.knownPeers.set(peer.peerId, peerInfo);
|
|
6689
|
+
this.saveCachedPeers();
|
|
6583
6690
|
}
|
|
6584
6691
|
} catch (err) {
|
|
6585
6692
|
console.warn("[ByteCave] Refresh: Failed to get health from peer:", peer.peerId.slice(0, 12), err.message);
|
|
@@ -6644,15 +6751,81 @@ Nonce: ${nonce}`;
|
|
|
6644
6751
|
console.warn("[ByteCave] Failed to create authorization:", err.message);
|
|
6645
6752
|
}
|
|
6646
6753
|
}
|
|
6647
|
-
if (
|
|
6648
|
-
|
|
6754
|
+
if (this.config.relayWsUrl) {
|
|
6755
|
+
console.log("[ByteCave] Attempting storage via WebSocket relay");
|
|
6756
|
+
try {
|
|
6757
|
+
if (!this.storageWsClient) {
|
|
6758
|
+
this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
|
|
6759
|
+
}
|
|
6760
|
+
const wsAuth = authorization ? {
|
|
6761
|
+
signature: authorization.signature,
|
|
6762
|
+
address: authorization.sender,
|
|
6763
|
+
timestamp: authorization.timestamp,
|
|
6764
|
+
nonce: authorization.nonce,
|
|
6765
|
+
appId: authorization.appId,
|
|
6766
|
+
contentHash: authorization.contentHash
|
|
6767
|
+
} : void 0;
|
|
6768
|
+
const result = await this.storageWsClient.store({
|
|
6769
|
+
data: dataArray,
|
|
6770
|
+
contentType: mimeType || "application/octet-stream",
|
|
6771
|
+
hashIdToken,
|
|
6772
|
+
authorization: wsAuth,
|
|
6773
|
+
timeout: 3e4
|
|
6774
|
+
});
|
|
6775
|
+
if (result.success && result.cid) {
|
|
6776
|
+
console.log("[ByteCave] \u2713 WebSocket storage successful:", result.cid);
|
|
6777
|
+
return {
|
|
6778
|
+
success: true,
|
|
6779
|
+
cid: result.cid,
|
|
6780
|
+
peerId: "relay-ws"
|
|
6781
|
+
};
|
|
6782
|
+
}
|
|
6783
|
+
console.warn("[ByteCave] WebSocket storage failed, trying WebTransport:", result.error);
|
|
6784
|
+
} catch (err) {
|
|
6785
|
+
console.warn("[ByteCave] WebSocket storage exception, trying WebTransport:", err.message);
|
|
6786
|
+
}
|
|
6787
|
+
} else {
|
|
6788
|
+
console.log("[ByteCave] WebSocket relay not configured, trying WebTransport");
|
|
6649
6789
|
}
|
|
6650
|
-
console.log("[ByteCave]
|
|
6790
|
+
console.log("[ByteCave] Attempting storage via WebTransport direct");
|
|
6651
6791
|
try {
|
|
6652
|
-
|
|
6653
|
-
|
|
6792
|
+
let nodes = Array.from(this.knownPeers.values());
|
|
6793
|
+
if (nodes.length === 0) {
|
|
6794
|
+
console.log("[ByteCave] No cached peers, trying discovery services");
|
|
6795
|
+
if (this.relayDiscovery) {
|
|
6796
|
+
nodes = await this.relayDiscovery.getConnectedPeers();
|
|
6797
|
+
} else if (this.contractDiscovery) {
|
|
6798
|
+
nodes = await this.contractDiscovery.getActiveNodes();
|
|
6799
|
+
}
|
|
6800
|
+
} else {
|
|
6801
|
+
console.log("[ByteCave] Using", nodes.length, "cached peers for WebTransport");
|
|
6654
6802
|
}
|
|
6655
|
-
|
|
6803
|
+
if (nodes.length === 0) {
|
|
6804
|
+
return {
|
|
6805
|
+
success: false,
|
|
6806
|
+
error: "No nodes available for WebTransport storage"
|
|
6807
|
+
};
|
|
6808
|
+
}
|
|
6809
|
+
const nodeWithWebTransport = nodes.find(
|
|
6810
|
+
(node) => node.multiaddrs?.some((addr) => addr.includes("/webtransport"))
|
|
6811
|
+
);
|
|
6812
|
+
if (!nodeWithWebTransport || !nodeWithWebTransport.multiaddrs) {
|
|
6813
|
+
return {
|
|
6814
|
+
success: false,
|
|
6815
|
+
error: "No nodes with WebTransport support found"
|
|
6816
|
+
};
|
|
6817
|
+
}
|
|
6818
|
+
const wtMultiaddr = nodeWithWebTransport.multiaddrs.find(
|
|
6819
|
+
(addr) => addr.includes("/webtransport")
|
|
6820
|
+
);
|
|
6821
|
+
if (!wtMultiaddr) {
|
|
6822
|
+
return {
|
|
6823
|
+
success: false,
|
|
6824
|
+
error: "No WebTransport multiaddr found"
|
|
6825
|
+
};
|
|
6826
|
+
}
|
|
6827
|
+
const wtClient = new StorageWebTransportClient(wtMultiaddr);
|
|
6828
|
+
const wtAuth = authorization ? {
|
|
6656
6829
|
signature: authorization.signature,
|
|
6657
6830
|
address: authorization.sender,
|
|
6658
6831
|
timestamp: authorization.timestamp,
|
|
@@ -6660,26 +6833,25 @@ Nonce: ${nonce}`;
|
|
|
6660
6833
|
appId: authorization.appId,
|
|
6661
6834
|
contentHash: authorization.contentHash
|
|
6662
6835
|
} : void 0;
|
|
6663
|
-
const result = await
|
|
6836
|
+
const result = await wtClient.store({
|
|
6664
6837
|
data: dataArray,
|
|
6665
6838
|
contentType: mimeType || "application/octet-stream",
|
|
6666
6839
|
hashIdToken,
|
|
6667
|
-
authorization:
|
|
6668
|
-
timeout: 3e4
|
|
6840
|
+
authorization: wtAuth
|
|
6669
6841
|
});
|
|
6670
6842
|
if (result.success && result.cid) {
|
|
6671
|
-
console.log("[ByteCave] \u2713
|
|
6843
|
+
console.log("[ByteCave] \u2713 WebTransport storage successful:", result.cid);
|
|
6672
6844
|
return {
|
|
6673
6845
|
success: true,
|
|
6674
6846
|
cid: result.cid,
|
|
6675
|
-
peerId:
|
|
6847
|
+
peerId: nodeWithWebTransport.peerId
|
|
6676
6848
|
};
|
|
6677
6849
|
}
|
|
6678
|
-
console.
|
|
6679
|
-
return { success: false, error: result.error || "
|
|
6850
|
+
console.error("[ByteCave] WebTransport storage failed:", result.error);
|
|
6851
|
+
return { success: false, error: result.error || "All storage methods failed" };
|
|
6680
6852
|
} catch (err) {
|
|
6681
|
-
console.error("[ByteCave]
|
|
6682
|
-
return { success: false, error: err.message };
|
|
6853
|
+
console.error("[ByteCave] WebTransport storage exception:", err.message);
|
|
6854
|
+
return { success: false, error: `All storage methods failed: ${err.message}` };
|
|
6683
6855
|
}
|
|
6684
6856
|
}
|
|
6685
6857
|
/**
|
|
@@ -6944,8 +7116,38 @@ Nonce: ${nonce}`;
|
|
|
6944
7116
|
onChainNodeId: announcement.onChainNodeId
|
|
6945
7117
|
};
|
|
6946
7118
|
this.knownPeers.set(announcement.peerId, peerInfo);
|
|
7119
|
+
this.saveCachedPeers();
|
|
6947
7120
|
this.emit("peerAnnounce", peerInfo);
|
|
6948
7121
|
}
|
|
7122
|
+
/**
|
|
7123
|
+
* Load cached peers from localStorage
|
|
7124
|
+
*/
|
|
7125
|
+
loadCachedPeers() {
|
|
7126
|
+
try {
|
|
7127
|
+
const cached = localStorage.getItem(this.PEERS_CACHE_KEY);
|
|
7128
|
+
if (cached) {
|
|
7129
|
+
const peers = JSON.parse(cached);
|
|
7130
|
+
console.log("[ByteCave] Loaded", peers.length, "cached peers from localStorage");
|
|
7131
|
+
for (const peer of peers) {
|
|
7132
|
+
this.knownPeers.set(peer.peerId, { ...peer, connected: false });
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
} catch (error) {
|
|
7136
|
+
console.warn("[ByteCave] Failed to load cached peers:", error);
|
|
7137
|
+
}
|
|
7138
|
+
}
|
|
7139
|
+
/**
|
|
7140
|
+
* Save known peers to localStorage
|
|
7141
|
+
*/
|
|
7142
|
+
saveCachedPeers() {
|
|
7143
|
+
try {
|
|
7144
|
+
const peers = Array.from(this.knownPeers.values());
|
|
7145
|
+
localStorage.setItem(this.PEERS_CACHE_KEY, JSON.stringify(peers));
|
|
7146
|
+
console.log("[ByteCave] Saved", peers.length, "peers to localStorage");
|
|
7147
|
+
} catch (error) {
|
|
7148
|
+
console.warn("[ByteCave] Failed to save cached peers:", error);
|
|
7149
|
+
}
|
|
7150
|
+
}
|
|
6949
7151
|
/**
|
|
6950
7152
|
* Check if a nodeId is registered in the on-chain registry
|
|
6951
7153
|
*/
|
package/dist/index.js
CHANGED
package/dist/react/index.js
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebTransport Storage Client
|
|
3
|
+
*
|
|
4
|
+
* Provides direct browser-to-node storage over WebTransport (HTTP/3)
|
|
5
|
+
* Used as fallback when WebSocket relay is unavailable
|
|
6
|
+
*/
|
|
7
|
+
export interface WebTransportStorageRequest {
|
|
8
|
+
data: Uint8Array;
|
|
9
|
+
contentType: string;
|
|
10
|
+
hashIdToken?: number;
|
|
11
|
+
authorization?: {
|
|
12
|
+
signature: string;
|
|
13
|
+
address: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
nonce: string;
|
|
16
|
+
appId: string;
|
|
17
|
+
contentHash: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface WebTransportStorageResponse {
|
|
21
|
+
success: boolean;
|
|
22
|
+
cid?: string;
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare class StorageWebTransportClient {
|
|
26
|
+
private nodeMultiaddr;
|
|
27
|
+
constructor(nodeMultiaddr: string);
|
|
28
|
+
/**
|
|
29
|
+
* Store data via WebTransport direct to node
|
|
30
|
+
*/
|
|
31
|
+
store(request: WebTransportStorageRequest): Promise<WebTransportStorageResponse>;
|
|
32
|
+
/**
|
|
33
|
+
* Convert libp2p multiaddr to WebTransport URL
|
|
34
|
+
*/
|
|
35
|
+
private multiaddrToWebTransportUrl;
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gethashd/bytecave-browser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.49",
|
|
4
4
|
"description": "ByteCave browser client for WebRTC P2P connections to storage nodes",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@libp2p/identify": "^4.0.9",
|
|
46
46
|
"@libp2p/webrtc": "^6.0.10",
|
|
47
47
|
"@libp2p/websockets": "^10.1.2",
|
|
48
|
+
"@libp2p/webtransport": "^6.0.12",
|
|
48
49
|
"@multiformats/multiaddr": "^13.0.1",
|
|
49
50
|
"ethers": "^6.0.0",
|
|
50
51
|
"libp2p": "^3.1.2",
|
package/src/client.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { ethers } from 'ethers';
|
|
|
21
21
|
import { ContractDiscovery, RelayDiscovery } from './discovery.js';
|
|
22
22
|
import { p2pProtocolClient } from './p2p-protocols.js';
|
|
23
23
|
import { StorageWebSocketClient } from './storage-websocket.js';
|
|
24
|
+
import { StorageWebTransportClient } from './storage-webtransport.js';
|
|
24
25
|
import { CONTENT_REGISTRY_ABI } from './contracts/ContentRegistry.js';
|
|
25
26
|
import type {
|
|
26
27
|
ByteCaveConfig,
|
|
@@ -43,6 +44,7 @@ export class ByteCaveClient {
|
|
|
43
44
|
private knownPeers: Map<string, PeerInfo> = new Map();
|
|
44
45
|
private connectionState: ConnectionState = 'disconnected';
|
|
45
46
|
private eventListeners: Map<string, Set<Function>> = new Map();
|
|
47
|
+
private readonly PEERS_CACHE_KEY = 'bytecave_known_peers';
|
|
46
48
|
|
|
47
49
|
constructor(config: ByteCaveConfig) {
|
|
48
50
|
this.config = {
|
|
@@ -61,6 +63,9 @@ export class ByteCaveClient {
|
|
|
61
63
|
if (config.vaultNodeRegistryAddress && config.rpcUrl) {
|
|
62
64
|
this.contractDiscovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
|
|
63
65
|
}
|
|
66
|
+
|
|
67
|
+
// Load cached peers from localStorage
|
|
68
|
+
this.loadCachedPeers();
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
/**
|
|
@@ -276,14 +281,19 @@ export class ByteCaveClient {
|
|
|
276
281
|
// Fetch health data
|
|
277
282
|
const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
|
|
278
283
|
if (health) {
|
|
279
|
-
|
|
284
|
+
const peerInfo = {
|
|
280
285
|
peerId: peer.peerId,
|
|
281
286
|
publicKey: health.publicKey || '',
|
|
282
287
|
contentTypes: health.contentTypes || 'all',
|
|
288
|
+
httpEndpoint: (health as any).httpEndpoint || '',
|
|
289
|
+
multiaddrs: peer.multiaddrs || [],
|
|
290
|
+
relayAddrs: (peer as any).relayAddrs || [],
|
|
283
291
|
connected: true,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
292
|
+
isRegistered: (peer as any).isRegistered || false,
|
|
293
|
+
onChainNodeId: (peer as any).onChainNodeId || ''
|
|
294
|
+
};
|
|
295
|
+
this.knownPeers.set(peer.peerId, peerInfo);
|
|
296
|
+
this.saveCachedPeers();
|
|
287
297
|
}
|
|
288
298
|
} catch (err: any) {
|
|
289
299
|
console.warn('[ByteCave] Failed to process peer from directory:', peer.peerId.slice(0, 12), err.message);
|
|
@@ -390,14 +400,19 @@ export class ByteCaveClient {
|
|
|
390
400
|
try {
|
|
391
401
|
const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
|
|
392
402
|
if (health) {
|
|
393
|
-
|
|
403
|
+
const peerInfo = {
|
|
394
404
|
peerId: peer.peerId,
|
|
395
405
|
publicKey: health.publicKey || '',
|
|
396
406
|
contentTypes: health.contentTypes || 'all',
|
|
407
|
+
httpEndpoint: (health as any).httpEndpoint || '',
|
|
408
|
+
multiaddrs: (peer as any).multiaddrs || [],
|
|
409
|
+
relayAddrs: (peer as any).relayAddrs || [],
|
|
397
410
|
connected: true,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
411
|
+
isRegistered: (peer as any).isRegistered || false,
|
|
412
|
+
onChainNodeId: (peer as any).onChainNodeId || ''
|
|
413
|
+
};
|
|
414
|
+
this.knownPeers.set(peer.peerId, peerInfo);
|
|
415
|
+
this.saveCachedPeers();
|
|
401
416
|
}
|
|
402
417
|
} catch (err: any) {
|
|
403
418
|
console.warn('[ByteCave] Refresh: Failed to get health from peer:', peer.peerId.slice(0, 12), err.message);
|
|
@@ -476,19 +491,103 @@ Nonce: ${nonce}`;
|
|
|
476
491
|
}
|
|
477
492
|
}
|
|
478
493
|
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
494
|
+
// Storage fallback strategy:
|
|
495
|
+
// 1. Try WebSocket relay (most reliable)
|
|
496
|
+
// 2. If relay fails, try WebTransport direct to node
|
|
497
|
+
|
|
498
|
+
// Try WebSocket relay first
|
|
499
|
+
if (this.config.relayWsUrl) {
|
|
500
|
+
console.log('[ByteCave] Attempting storage via WebSocket relay');
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
if (!this.storageWsClient) {
|
|
504
|
+
this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const wsAuth = authorization ? {
|
|
508
|
+
signature: authorization.signature,
|
|
509
|
+
address: authorization.sender,
|
|
510
|
+
timestamp: authorization.timestamp,
|
|
511
|
+
nonce: authorization.nonce,
|
|
512
|
+
appId: authorization.appId,
|
|
513
|
+
contentHash: authorization.contentHash
|
|
514
|
+
} : undefined;
|
|
515
|
+
|
|
516
|
+
const result = await this.storageWsClient.store({
|
|
517
|
+
data: dataArray,
|
|
518
|
+
contentType: mimeType || 'application/octet-stream',
|
|
519
|
+
hashIdToken,
|
|
520
|
+
authorization: wsAuth,
|
|
521
|
+
timeout: 30000
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
if (result.success && result.cid) {
|
|
525
|
+
console.log('[ByteCave] ✓ WebSocket storage successful:', result.cid);
|
|
526
|
+
return {
|
|
527
|
+
success: true,
|
|
528
|
+
cid: result.cid,
|
|
529
|
+
peerId: 'relay-ws'
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
console.warn('[ByteCave] WebSocket storage failed, trying WebTransport:', result.error);
|
|
534
|
+
} catch (err: any) {
|
|
535
|
+
console.warn('[ByteCave] WebSocket storage exception, trying WebTransport:', err.message);
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
console.log('[ByteCave] WebSocket relay not configured, trying WebTransport');
|
|
482
539
|
}
|
|
483
540
|
|
|
484
|
-
|
|
541
|
+
// Fallback to WebTransport direct to node
|
|
542
|
+
console.log('[ByteCave] Attempting storage via WebTransport direct');
|
|
485
543
|
|
|
486
544
|
try {
|
|
487
|
-
|
|
488
|
-
|
|
545
|
+
// Try cached peers first, then discovery services
|
|
546
|
+
let nodes: any[] = Array.from(this.knownPeers.values());
|
|
547
|
+
|
|
548
|
+
if (nodes.length === 0) {
|
|
549
|
+
console.log('[ByteCave] No cached peers, trying discovery services');
|
|
550
|
+
if (this.relayDiscovery) {
|
|
551
|
+
nodes = await this.relayDiscovery.getConnectedPeers();
|
|
552
|
+
} else if (this.contractDiscovery) {
|
|
553
|
+
nodes = await this.contractDiscovery.getActiveNodes();
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
console.log('[ByteCave] Using', nodes.length, 'cached peers for WebTransport');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (nodes.length === 0) {
|
|
560
|
+
return {
|
|
561
|
+
success: false,
|
|
562
|
+
error: 'No nodes available for WebTransport storage'
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const nodeWithWebTransport = nodes.find((node: any) =>
|
|
567
|
+
node.multiaddrs?.some((addr: string) => addr.includes('/webtransport'))
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
if (!nodeWithWebTransport || !nodeWithWebTransport.multiaddrs) {
|
|
571
|
+
return {
|
|
572
|
+
success: false,
|
|
573
|
+
error: 'No nodes with WebTransport support found'
|
|
574
|
+
};
|
|
489
575
|
}
|
|
490
576
|
|
|
491
|
-
const
|
|
577
|
+
const wtMultiaddr = nodeWithWebTransport.multiaddrs.find((addr: string) =>
|
|
578
|
+
addr.includes('/webtransport')
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
if (!wtMultiaddr) {
|
|
582
|
+
return {
|
|
583
|
+
success: false,
|
|
584
|
+
error: 'No WebTransport multiaddr found'
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const wtClient = new StorageWebTransportClient(wtMultiaddr);
|
|
589
|
+
|
|
590
|
+
const wtAuth = authorization ? {
|
|
492
591
|
signature: authorization.signature,
|
|
493
592
|
address: authorization.sender,
|
|
494
593
|
timestamp: authorization.timestamp,
|
|
@@ -497,28 +596,27 @@ Nonce: ${nonce}`;
|
|
|
497
596
|
contentHash: authorization.contentHash
|
|
498
597
|
} : undefined;
|
|
499
598
|
|
|
500
|
-
const result = await
|
|
599
|
+
const result = await wtClient.store({
|
|
501
600
|
data: dataArray,
|
|
502
601
|
contentType: mimeType || 'application/octet-stream',
|
|
503
602
|
hashIdToken,
|
|
504
|
-
authorization:
|
|
505
|
-
timeout: 30000
|
|
603
|
+
authorization: wtAuth
|
|
506
604
|
});
|
|
507
605
|
|
|
508
606
|
if (result.success && result.cid) {
|
|
509
|
-
console.log('[ByteCave] ✓
|
|
607
|
+
console.log('[ByteCave] ✓ WebTransport storage successful:', result.cid);
|
|
510
608
|
return {
|
|
511
609
|
success: true,
|
|
512
610
|
cid: result.cid,
|
|
513
|
-
peerId:
|
|
611
|
+
peerId: nodeWithWebTransport.peerId
|
|
514
612
|
};
|
|
515
613
|
}
|
|
516
614
|
|
|
517
|
-
console.
|
|
518
|
-
return { success: false, error: result.error || '
|
|
615
|
+
console.error('[ByteCave] WebTransport storage failed:', result.error);
|
|
616
|
+
return { success: false, error: result.error || 'All storage methods failed' };
|
|
519
617
|
} catch (err: any) {
|
|
520
|
-
console.error('[ByteCave]
|
|
521
|
-
return { success: false, error: err.message };
|
|
618
|
+
console.error('[ByteCave] WebTransport storage exception:', err.message);
|
|
619
|
+
return { success: false, error: `All storage methods failed: ${err.message}` };
|
|
522
620
|
}
|
|
523
621
|
}
|
|
524
622
|
|
|
@@ -855,11 +953,43 @@ Nonce: ${nonce}`;
|
|
|
855
953
|
};
|
|
856
954
|
|
|
857
955
|
this.knownPeers.set(announcement.peerId, peerInfo);
|
|
956
|
+
this.saveCachedPeers();
|
|
858
957
|
|
|
859
958
|
this.emit('peerAnnounce', peerInfo);
|
|
860
959
|
}
|
|
861
960
|
|
|
862
961
|
|
|
962
|
+
/**
|
|
963
|
+
* Load cached peers from localStorage
|
|
964
|
+
*/
|
|
965
|
+
private loadCachedPeers(): void {
|
|
966
|
+
try {
|
|
967
|
+
const cached = localStorage.getItem(this.PEERS_CACHE_KEY);
|
|
968
|
+
if (cached) {
|
|
969
|
+
const peers = JSON.parse(cached);
|
|
970
|
+
console.log('[ByteCave] Loaded', peers.length, 'cached peers from localStorage');
|
|
971
|
+
for (const peer of peers) {
|
|
972
|
+
this.knownPeers.set(peer.peerId, { ...peer, connected: false });
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
} catch (error) {
|
|
976
|
+
console.warn('[ByteCave] Failed to load cached peers:', error);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Save known peers to localStorage
|
|
982
|
+
*/
|
|
983
|
+
private saveCachedPeers(): void {
|
|
984
|
+
try {
|
|
985
|
+
const peers = Array.from(this.knownPeers.values());
|
|
986
|
+
localStorage.setItem(this.PEERS_CACHE_KEY, JSON.stringify(peers));
|
|
987
|
+
console.log('[ByteCave] Saved', peers.length, 'peers to localStorage');
|
|
988
|
+
} catch (error) {
|
|
989
|
+
console.warn('[ByteCave] Failed to save cached peers:', error);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
863
993
|
/**
|
|
864
994
|
* Check if a nodeId is registered in the on-chain registry
|
|
865
995
|
*/
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebTransport Storage Client
|
|
3
|
+
*
|
|
4
|
+
* Provides direct browser-to-node storage over WebTransport (HTTP/3)
|
|
5
|
+
* Used as fallback when WebSocket relay is unavailable
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Logger removed - using console.log for browser compatibility
|
|
9
|
+
|
|
10
|
+
export interface WebTransportStorageRequest {
|
|
11
|
+
data: Uint8Array;
|
|
12
|
+
contentType: string;
|
|
13
|
+
hashIdToken?: number;
|
|
14
|
+
authorization?: {
|
|
15
|
+
signature: string;
|
|
16
|
+
address: string;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
nonce: string;
|
|
19
|
+
appId: string;
|
|
20
|
+
contentHash: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface WebTransportStorageResponse {
|
|
25
|
+
success: boolean;
|
|
26
|
+
cid?: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class StorageWebTransportClient {
|
|
31
|
+
private nodeMultiaddr: string;
|
|
32
|
+
|
|
33
|
+
constructor(nodeMultiaddr: string) {
|
|
34
|
+
this.nodeMultiaddr = nodeMultiaddr;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Store data via WebTransport direct to node
|
|
39
|
+
*/
|
|
40
|
+
async store(request: WebTransportStorageRequest): Promise<WebTransportStorageResponse> {
|
|
41
|
+
try {
|
|
42
|
+
// Check if WebTransport is supported
|
|
43
|
+
if (typeof WebTransport === 'undefined') {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: 'WebTransport not supported in this browser'
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Parse multiaddr to get WebTransport URL
|
|
51
|
+
// Format: /ip4/127.0.0.1/udp/4001/quic-v1/webtransport/certhash/...
|
|
52
|
+
const url = this.multiaddrToWebTransportUrl(this.nodeMultiaddr);
|
|
53
|
+
if (!url) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: 'Invalid WebTransport multiaddr'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log('[WebTransport] Connecting to node:', url);
|
|
61
|
+
|
|
62
|
+
// Create WebTransport connection
|
|
63
|
+
const transport = new WebTransport(url);
|
|
64
|
+
await transport.ready;
|
|
65
|
+
|
|
66
|
+
console.log('[WebTransport] Connected, opening bidirectional stream');
|
|
67
|
+
|
|
68
|
+
// Open bidirectional stream for storage protocol
|
|
69
|
+
const stream = await transport.createBidirectionalStream();
|
|
70
|
+
const writer = stream.writable.getWriter();
|
|
71
|
+
const reader = stream.readable.getReader();
|
|
72
|
+
|
|
73
|
+
// Send storage request
|
|
74
|
+
const requestData = {
|
|
75
|
+
type: 'storage-request',
|
|
76
|
+
data: Array.from(request.data), // Convert Uint8Array to array for JSON
|
|
77
|
+
contentType: request.contentType,
|
|
78
|
+
hashIdToken: request.hashIdToken,
|
|
79
|
+
authorization: request.authorization
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const requestJson = JSON.stringify(requestData);
|
|
83
|
+
const requestBytes = new TextEncoder().encode(requestJson);
|
|
84
|
+
|
|
85
|
+
await writer.write(requestBytes);
|
|
86
|
+
await writer.close();
|
|
87
|
+
|
|
88
|
+
console.log('[WebTransport] Request sent, waiting for response');
|
|
89
|
+
|
|
90
|
+
// Read response
|
|
91
|
+
const { value, done } = await reader.read();
|
|
92
|
+
if (done || !value) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: 'No response from node'
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const responseJson = new TextDecoder().decode(value);
|
|
100
|
+
const response = JSON.parse(responseJson);
|
|
101
|
+
|
|
102
|
+
console.log('[WebTransport] Response received:', response);
|
|
103
|
+
|
|
104
|
+
// Close transport
|
|
105
|
+
await transport.close();
|
|
106
|
+
|
|
107
|
+
return response;
|
|
108
|
+
|
|
109
|
+
} catch (error: any) {
|
|
110
|
+
console.error('[WebTransport] Storage failed:', error);
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
error: error.message || 'WebTransport storage failed'
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Convert libp2p multiaddr to WebTransport URL
|
|
120
|
+
*/
|
|
121
|
+
private multiaddrToWebTransportUrl(multiaddr: string): string | null {
|
|
122
|
+
try {
|
|
123
|
+
// Parse multiaddr format: /ip4/127.0.0.1/udp/4001/quic-v1/webtransport
|
|
124
|
+
const parts = multiaddr.split('/').filter(p => p);
|
|
125
|
+
|
|
126
|
+
let ip = '';
|
|
127
|
+
let port = '';
|
|
128
|
+
let hasQuic = false;
|
|
129
|
+
let hasWebTransport = false;
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < parts.length; i++) {
|
|
132
|
+
if (parts[i] === 'ip4' && i + 1 < parts.length) {
|
|
133
|
+
ip = parts[i + 1];
|
|
134
|
+
} else if (parts[i] === 'udp' && i + 1 < parts.length) {
|
|
135
|
+
port = parts[i + 1];
|
|
136
|
+
} else if (parts[i] === 'quic-v1') {
|
|
137
|
+
hasQuic = true;
|
|
138
|
+
} else if (parts[i] === 'webtransport') {
|
|
139
|
+
hasWebTransport = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!ip || !port || !hasQuic || !hasWebTransport) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// WebTransport URL format: https://ip:port
|
|
148
|
+
// Note: In production, this needs proper certificate handling
|
|
149
|
+
return `https://${ip}:${port}`;
|
|
150
|
+
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error('[WebTransport] Failed to parse multiaddr:', error);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|