@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.
@@ -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
- this.knownPeers.set(peer.peerId, {
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
- nodeId: health.nodeId
6432
- });
6433
- console.log("[ByteCave] \u2713 Discovered peer:", health.nodeId || peer.peerId.slice(0, 12));
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
- this.knownPeers.set(peer.peerId, {
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
- nodeId: health.nodeId
6528
- });
6529
- console.log("[ByteCave] Refresh: \u2713 Updated peer info:", health.nodeId || peer.peerId.slice(0, 12));
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 (!this.config.relayWsUrl) {
6595
- return { success: false, error: "WebSocket relay URL not configured" };
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] Storing via WebSocket relay");
6737
+ console.log("[ByteCave] Attempting storage via WebTransport direct");
6598
6738
  try {
6599
- if (!this.storageWsClient) {
6600
- this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
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
- const wsAuth = authorization ? {
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 this.storageWsClient.store({
6783
+ const result = await wtClient.store({
6611
6784
  data: dataArray,
6612
6785
  contentType: mimeType || "application/octet-stream",
6613
6786
  hashIdToken,
6614
- authorization: wsAuth,
6615
- timeout: 3e4
6787
+ authorization: wtAuth
6616
6788
  });
6617
6789
  if (result.success && result.cid) {
6618
- console.log("[ByteCave] \u2713 WebSocket storage successful:", result.cid);
6790
+ console.log("[ByteCave] \u2713 WebTransport storage successful:", result.cid);
6619
6791
  return {
6620
6792
  success: true,
6621
6793
  cid: result.cid,
6622
- peerId: "relay-ws"
6794
+ peerId: nodeWithWebTransport.peerId
6623
6795
  };
6624
6796
  }
6625
- console.warn("[ByteCave] WebSocket storage failed:", result.error);
6626
- return { success: false, error: result.error || "WebSocket storage failed" };
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] WebSocket storage exception:", err.message);
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
- this.knownPeers.set(peer.peerId, {
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
- nodeId: health.nodeId
6485
- });
6486
- console.log("[ByteCave] \u2713 Discovered peer:", health.nodeId || peer.peerId.slice(0, 12));
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
- this.knownPeers.set(peer.peerId, {
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
- nodeId: health.nodeId
6581
- });
6582
- console.log("[ByteCave] Refresh: \u2713 Updated peer info:", health.nodeId || peer.peerId.slice(0, 12));
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 (!this.config.relayWsUrl) {
6648
- return { success: false, error: "WebSocket relay URL not configured" };
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] Storing via WebSocket relay");
6790
+ console.log("[ByteCave] Attempting storage via WebTransport direct");
6651
6791
  try {
6652
- if (!this.storageWsClient) {
6653
- this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
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
- const wsAuth = authorization ? {
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 this.storageWsClient.store({
6836
+ const result = await wtClient.store({
6664
6837
  data: dataArray,
6665
6838
  contentType: mimeType || "application/octet-stream",
6666
6839
  hashIdToken,
6667
- authorization: wsAuth,
6668
- timeout: 3e4
6840
+ authorization: wtAuth
6669
6841
  });
6670
6842
  if (result.success && result.cid) {
6671
- console.log("[ByteCave] \u2713 WebSocket storage successful:", result.cid);
6843
+ console.log("[ByteCave] \u2713 WebTransport storage successful:", result.cid);
6672
6844
  return {
6673
6845
  success: true,
6674
6846
  cid: result.cid,
6675
- peerId: "relay-ws"
6847
+ peerId: nodeWithWebTransport.peerId
6676
6848
  };
6677
6849
  }
6678
- console.warn("[ByteCave] WebSocket storage failed:", result.error);
6679
- return { success: false, error: result.error || "WebSocket storage failed" };
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] WebSocket storage exception:", err.message);
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
@@ -13,7 +13,7 @@ import {
13
13
  useHashdImage,
14
14
  useHashdMedia,
15
15
  useHashdUrl
16
- } from "./chunk-FOQ3CE3H.js";
16
+ } from "./chunk-CVBYHRQD.js";
17
17
  import {
18
18
  clearHashdCache,
19
19
  createHashdUrl,
@@ -8,7 +8,7 @@ import {
8
8
  useHashdImage,
9
9
  useHashdMedia,
10
10
  useHashdUrl
11
- } from "../chunk-FOQ3CE3H.js";
11
+ } from "../chunk-CVBYHRQD.js";
12
12
  import "../chunk-EEZWRIUI.js";
13
13
  export {
14
14
  HashdAudio,
@@ -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.47",
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
- this.knownPeers.set(peer.peerId, {
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
- nodeId: health.nodeId
285
- });
286
- console.log('[ByteCave] ✓ Discovered peer:', health.nodeId || peer.peerId.slice(0, 12));
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
- this.knownPeers.set(peer.peerId, {
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
- nodeId: health.nodeId
399
- });
400
- console.log('[ByteCave] Refresh: ✓ Updated peer info:', health.nodeId || peer.peerId.slice(0, 12));
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
- // Use WebSocket relay for storage (simpler connection management than direct P2P)
480
- if (!this.config.relayWsUrl) {
481
- return { success: false, error: 'WebSocket relay URL not configured' };
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
- console.log('[ByteCave] Storing via WebSocket relay');
541
+ // Fallback to WebTransport direct to node
542
+ console.log('[ByteCave] Attempting storage via WebTransport direct');
485
543
 
486
544
  try {
487
- if (!this.storageWsClient) {
488
- this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
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 wsAuth = authorization ? {
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 this.storageWsClient.store({
599
+ const result = await wtClient.store({
501
600
  data: dataArray,
502
601
  contentType: mimeType || 'application/octet-stream',
503
602
  hashIdToken,
504
- authorization: wsAuth,
505
- timeout: 30000
603
+ authorization: wtAuth
506
604
  });
507
605
 
508
606
  if (result.success && result.cid) {
509
- console.log('[ByteCave] ✓ WebSocket storage successful:', result.cid);
607
+ console.log('[ByteCave] ✓ WebTransport storage successful:', result.cid);
510
608
  return {
511
609
  success: true,
512
610
  cid: result.cid,
513
- peerId: 'relay-ws'
611
+ peerId: nodeWithWebTransport.peerId
514
612
  };
515
613
  }
516
614
 
517
- console.warn('[ByteCave] WebSocket storage failed:', result.error);
518
- return { success: false, error: result.error || 'WebSocket storage failed' };
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] WebSocket storage exception:', err.message);
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
+ }