@gethashd/bytecave-browser 1.0.2 → 1.0.3

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.
@@ -6,6 +6,29 @@ import {
6
6
 
7
7
  // src/discovery.ts
8
8
  import { ethers } from "ethers";
9
+ var RelayDiscovery = class {
10
+ constructor(relayHttpUrl) {
11
+ this.relayHttpUrl = relayHttpUrl;
12
+ }
13
+ /**
14
+ * Get currently connected storage nodes from relay
15
+ * Returns peer IDs and relay circuit multiaddrs
16
+ */
17
+ async getConnectedPeers() {
18
+ try {
19
+ const response = await fetch(`${this.relayHttpUrl}/peers`);
20
+ if (!response.ok) {
21
+ throw new Error(`Relay returned ${response.status}`);
22
+ }
23
+ const peers = await response.json();
24
+ console.log("[RelayDiscovery] Found connected peers:", peers.length);
25
+ return peers;
26
+ } catch (error) {
27
+ console.warn("[RelayDiscovery] Failed to fetch peers from relay:", error);
28
+ return [];
29
+ }
30
+ }
31
+ };
9
32
  var VAULT_REGISTRY_ABI = [
10
33
  "function getActiveNodes() external view returns (bytes32[] memory)",
11
34
  "function getNode(bytes32 nodeId) external view returns (tuple(address owner, bytes publicKey, string url, bytes32 metadataHash, uint256 registeredAt, bool active))",
@@ -5956,8 +5979,13 @@ var ByteCaveClient = class {
5956
5979
  connectionTimeout: 3e4,
5957
5980
  ...config
5958
5981
  };
5982
+ if (config.relayPeers && config.relayPeers.length > 0) {
5983
+ const relayHttpUrl = config.relayPeers[0];
5984
+ this.relayDiscovery = new RelayDiscovery(relayHttpUrl);
5985
+ console.log("[ByteCave] Using relay HTTP URL for peer discovery:", relayHttpUrl);
5986
+ }
5959
5987
  if (config.vaultNodeRegistryAddress && config.rpcUrl) {
5960
- this.discovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
5988
+ this.contractDiscovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
5961
5989
  }
5962
5990
  }
5963
5991
  /**
@@ -5976,12 +6004,8 @@ var ByteCaveClient = class {
5976
6004
  console.log("[ByteCave] Using direct node addresses:", this.config.directNodeAddrs);
5977
6005
  bootstrapPeers.push(...this.config.directNodeAddrs);
5978
6006
  }
5979
- if (this.config.relayPeers && this.config.relayPeers.length > 0) {
5980
- console.log("[ByteCave] Using relay peers as fallback:", this.config.relayPeers);
5981
- bootstrapPeers.push(...this.config.relayPeers);
5982
- }
5983
6007
  if (bootstrapPeers.length === 0) {
5984
- console.warn("[ByteCave] No peers configured - will rely on contract discovery only");
6008
+ console.warn("[ByteCave] No direct node addresses configured - will rely on relay peer discovery");
5985
6009
  }
5986
6010
  console.log("[ByteCave] Bootstrap peers:", bootstrapPeers);
5987
6011
  this.node = await createLibp2p({
@@ -6029,7 +6053,50 @@ var ByteCaveClient = class {
6029
6053
  const connectedPeers = this.node.getPeers();
6030
6054
  console.log("[ByteCave] Connected peers after relay dial:", connectedPeers.length, connectedPeers.map((p) => p.toString()));
6031
6055
  p2pProtocolClient.setNode(this.node);
6032
- console.log("[ByteCave] Querying relay for peer directory...");
6056
+ if (this.relayDiscovery) {
6057
+ console.log("[ByteCave] Querying relay HTTP endpoint for instant peer list...");
6058
+ try {
6059
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
6060
+ if (relayPeers.length > 0) {
6061
+ console.log("[ByteCave] Got", relayPeers.length, "peers from relay HTTP endpoint");
6062
+ for (const peer of relayPeers) {
6063
+ try {
6064
+ console.log("[ByteCave] Dialing peer:", peer.peerId.slice(0, 12) + "...");
6065
+ let connected = false;
6066
+ for (const addr of peer.multiaddrs) {
6067
+ try {
6068
+ const ma = multiaddr(addr);
6069
+ await this.node.dial(ma);
6070
+ connected = true;
6071
+ console.log("[ByteCave] \u2713 Connected via relay circuit");
6072
+ break;
6073
+ } catch (dialErr) {
6074
+ console.warn("[ByteCave] Failed to dial:", dialErr.message);
6075
+ }
6076
+ }
6077
+ if (connected) {
6078
+ const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
6079
+ if (health) {
6080
+ this.knownPeers.set(peer.peerId, {
6081
+ peerId: peer.peerId,
6082
+ publicKey: health.publicKey || "",
6083
+ contentTypes: health.contentTypes || "all",
6084
+ connected: true,
6085
+ nodeId: health.nodeId
6086
+ });
6087
+ console.log("[ByteCave] \u2713 Discovered peer via HTTP relay:", health.nodeId || peer.peerId.slice(0, 12));
6088
+ }
6089
+ }
6090
+ } catch (err) {
6091
+ console.warn("[ByteCave] Failed to process peer from HTTP relay:", err.message);
6092
+ }
6093
+ }
6094
+ }
6095
+ } catch (err) {
6096
+ console.warn("[ByteCave] HTTP relay discovery failed, falling back to P2P directory:", err.message);
6097
+ }
6098
+ }
6099
+ console.log("[ByteCave] Querying relay for peer directory via P2P...");
6033
6100
  for (const relayAddr of bootstrapPeers) {
6034
6101
  try {
6035
6102
  const parts = relayAddr.split("/p2p/");
@@ -6112,20 +6179,13 @@ var ByteCaveClient = class {
6112
6179
  console.warn("[ByteCave] Cannot refresh - node not initialized");
6113
6180
  return;
6114
6181
  }
6115
- const bootstrapPeers = [
6116
- ...this.config.directNodeAddrs || [],
6117
- ...this.config.relayPeers || []
6118
- ];
6119
- console.log("[ByteCave] Refreshing peer directory from relays...");
6120
- for (const relayAddr of bootstrapPeers) {
6182
+ console.log("[ByteCave] Refreshing peer directory from relay HTTP endpoint...");
6183
+ if (this.relayDiscovery) {
6121
6184
  try {
6122
- const parts = relayAddr.split("/p2p/");
6123
- if (parts.length < 2) continue;
6124
- const relayPeerId = parts[parts.length - 1];
6125
- const directory = await p2pProtocolClient.getPeerDirectoryFromRelay(relayPeerId);
6126
- if (directory && directory.peers.length > 0) {
6127
- console.log("[ByteCave] Refresh: Got", directory.peers.length, "peers from relay directory");
6128
- for (const peer of directory.peers) {
6185
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
6186
+ if (relayPeers.length > 0) {
6187
+ console.log("[ByteCave] Refresh: Got", relayPeers.length, "peers from relay HTTP endpoint");
6188
+ for (const peer of relayPeers) {
6129
6189
  const isConnected = this.node.getPeers().some((p) => p.toString() === peer.peerId);
6130
6190
  const knownPeer = this.knownPeers.get(peer.peerId);
6131
6191
  if (!isConnected || !knownPeer) {
@@ -6163,7 +6223,6 @@ var ByteCaveClient = class {
6163
6223
  console.debug("[ByteCave] Refresh: Peer already connected:", peer.peerId.slice(0, 12) + "...");
6164
6224
  }
6165
6225
  }
6166
- break;
6167
6226
  }
6168
6227
  } catch (err) {
6169
6228
  console.warn("[ByteCave] Refresh: Failed to get directory from relay:", err.message);
@@ -6184,10 +6243,7 @@ var ByteCaveClient = class {
6184
6243
  return { success: false, error: "P2P node not initialized" };
6185
6244
  }
6186
6245
  const allPeers = this.node.getPeers();
6187
- const relayPeerIds = new Set(
6188
- this.config.relayPeers?.map((addr) => addr.split("/p2p/").pop()) || []
6189
- );
6190
- const connectedPeerIds = allPeers.map((p) => p.toString()).filter((peerId) => !relayPeerIds.has(peerId));
6246
+ const connectedPeerIds = allPeers.map((p) => p.toString());
6191
6247
  console.log("[ByteCave] Store - connected storage peers:", connectedPeerIds.length);
6192
6248
  console.log("[ByteCave] Store - knownPeers with registration info:", this.knownPeers.size);
6193
6249
  if (connectedPeerIds.length === 0) {
@@ -6550,11 +6606,11 @@ Nonce: ${nonce}`;
6550
6606
  * Check if a nodeId is registered in the on-chain registry
6551
6607
  */
6552
6608
  async checkNodeRegistration(nodeId) {
6553
- if (!this.discovery) {
6609
+ if (!this.contractDiscovery) {
6554
6610
  return true;
6555
6611
  }
6556
6612
  try {
6557
- const registeredNodes = await this.discovery.getActiveNodes();
6613
+ const registeredNodes = await this.contractDiscovery.getActiveNodes();
6558
6614
  return registeredNodes.some((node) => node.nodeId === nodeId);
6559
6615
  } catch (error) {
6560
6616
  console.warn("[ByteCave] Failed to check node registration:", error);
@@ -7058,6 +7114,7 @@ function useHashdUrl(hashdUrl) {
7058
7114
  }
7059
7115
 
7060
7116
  export {
7117
+ RelayDiscovery,
7061
7118
  ContractDiscovery,
7062
7119
  p2pProtocolClient,
7063
7120
  ByteCaveClient,
package/dist/client.d.ts CHANGED
@@ -7,7 +7,8 @@ import { ethers } from 'ethers';
7
7
  import type { ByteCaveConfig, PeerInfo, StoreResult, RetrieveResult, ConnectionState, SignalingMessage } from './types.js';
8
8
  export declare class ByteCaveClient {
9
9
  private node;
10
- private discovery?;
10
+ private contractDiscovery?;
11
+ private relayDiscovery?;
11
12
  private config;
12
13
  private knownPeers;
13
14
  private connectionState;
@@ -1,10 +1,27 @@
1
1
  /**
2
- * Contract-based peer discovery
2
+ * Peer discovery for ByteCave browser clients
3
3
  *
4
- * Reads registered nodes from the VaultNodeRegistry smart contract
5
- * to bootstrap P2P connections without any central server.
4
+ * Supports two discovery methods:
5
+ * 1. RelayDiscovery - Fast: Query relay's /peers endpoint for instant peer list
6
+ * 2. ContractDiscovery - Slow: Read from VaultNodeRegistry smart contract
6
7
  */
7
8
  import type { NodeRegistryEntry } from './types.js';
9
+ /**
10
+ * Relay-based peer discovery (FAST)
11
+ * Queries the relay's HTTP endpoint for currently connected storage nodes
12
+ */
13
+ export declare class RelayDiscovery {
14
+ private relayHttpUrl;
15
+ constructor(relayHttpUrl: string);
16
+ /**
17
+ * Get currently connected storage nodes from relay
18
+ * Returns peer IDs and relay circuit multiaddrs
19
+ */
20
+ getConnectedPeers(): Promise<Array<{
21
+ peerId: string;
22
+ multiaddrs: string[];
23
+ }>>;
24
+ }
8
25
  export declare class ContractDiscovery {
9
26
  private provider;
10
27
  private contract;
package/dist/index.cjs CHANGED
@@ -38,6 +38,7 @@ __export(src_exports, {
38
38
  HashdAudio: () => HashdAudio,
39
39
  HashdImage: () => HashdImage,
40
40
  HashdVideo: () => HashdVideo,
41
+ RelayDiscovery: () => RelayDiscovery,
41
42
  TEST_EXPORT: () => TEST_EXPORT,
42
43
  clearHashdCache: () => clearHashdCache,
43
44
  createHashdUrl: () => createHashdUrl,
@@ -72,6 +73,29 @@ var import_ethers2 = require("ethers");
72
73
 
73
74
  // src/discovery.ts
74
75
  var import_ethers = require("ethers");
76
+ var RelayDiscovery = class {
77
+ constructor(relayHttpUrl) {
78
+ this.relayHttpUrl = relayHttpUrl;
79
+ }
80
+ /**
81
+ * Get currently connected storage nodes from relay
82
+ * Returns peer IDs and relay circuit multiaddrs
83
+ */
84
+ async getConnectedPeers() {
85
+ try {
86
+ const response = await fetch(`${this.relayHttpUrl}/peers`);
87
+ if (!response.ok) {
88
+ throw new Error(`Relay returned ${response.status}`);
89
+ }
90
+ const peers = await response.json();
91
+ console.log("[RelayDiscovery] Found connected peers:", peers.length);
92
+ return peers;
93
+ } catch (error) {
94
+ console.warn("[RelayDiscovery] Failed to fetch peers from relay:", error);
95
+ return [];
96
+ }
97
+ }
98
+ };
75
99
  var VAULT_REGISTRY_ABI = [
76
100
  "function getActiveNodes() external view returns (bytes32[] memory)",
77
101
  "function getNode(bytes32 nodeId) external view returns (tuple(address owner, bytes publicKey, string url, bytes32 metadataHash, uint256 registeredAt, bool active))",
@@ -6008,8 +6032,13 @@ var ByteCaveClient = class {
6008
6032
  connectionTimeout: 3e4,
6009
6033
  ...config
6010
6034
  };
6035
+ if (config.relayPeers && config.relayPeers.length > 0) {
6036
+ const relayHttpUrl = config.relayPeers[0];
6037
+ this.relayDiscovery = new RelayDiscovery(relayHttpUrl);
6038
+ console.log("[ByteCave] Using relay HTTP URL for peer discovery:", relayHttpUrl);
6039
+ }
6011
6040
  if (config.vaultNodeRegistryAddress && config.rpcUrl) {
6012
- this.discovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
6041
+ this.contractDiscovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
6013
6042
  }
6014
6043
  }
6015
6044
  /**
@@ -6028,12 +6057,8 @@ var ByteCaveClient = class {
6028
6057
  console.log("[ByteCave] Using direct node addresses:", this.config.directNodeAddrs);
6029
6058
  bootstrapPeers.push(...this.config.directNodeAddrs);
6030
6059
  }
6031
- if (this.config.relayPeers && this.config.relayPeers.length > 0) {
6032
- console.log("[ByteCave] Using relay peers as fallback:", this.config.relayPeers);
6033
- bootstrapPeers.push(...this.config.relayPeers);
6034
- }
6035
6060
  if (bootstrapPeers.length === 0) {
6036
- console.warn("[ByteCave] No peers configured - will rely on contract discovery only");
6061
+ console.warn("[ByteCave] No direct node addresses configured - will rely on relay peer discovery");
6037
6062
  }
6038
6063
  console.log("[ByteCave] Bootstrap peers:", bootstrapPeers);
6039
6064
  this.node = await (0, import_libp2p.createLibp2p)({
@@ -6081,7 +6106,50 @@ var ByteCaveClient = class {
6081
6106
  const connectedPeers = this.node.getPeers();
6082
6107
  console.log("[ByteCave] Connected peers after relay dial:", connectedPeers.length, connectedPeers.map((p) => p.toString()));
6083
6108
  p2pProtocolClient.setNode(this.node);
6084
- console.log("[ByteCave] Querying relay for peer directory...");
6109
+ if (this.relayDiscovery) {
6110
+ console.log("[ByteCave] Querying relay HTTP endpoint for instant peer list...");
6111
+ try {
6112
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
6113
+ if (relayPeers.length > 0) {
6114
+ console.log("[ByteCave] Got", relayPeers.length, "peers from relay HTTP endpoint");
6115
+ for (const peer of relayPeers) {
6116
+ try {
6117
+ console.log("[ByteCave] Dialing peer:", peer.peerId.slice(0, 12) + "...");
6118
+ let connected = false;
6119
+ for (const addr of peer.multiaddrs) {
6120
+ try {
6121
+ const ma = (0, import_multiaddr.multiaddr)(addr);
6122
+ await this.node.dial(ma);
6123
+ connected = true;
6124
+ console.log("[ByteCave] \u2713 Connected via relay circuit");
6125
+ break;
6126
+ } catch (dialErr) {
6127
+ console.warn("[ByteCave] Failed to dial:", dialErr.message);
6128
+ }
6129
+ }
6130
+ if (connected) {
6131
+ const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
6132
+ if (health) {
6133
+ this.knownPeers.set(peer.peerId, {
6134
+ peerId: peer.peerId,
6135
+ publicKey: health.publicKey || "",
6136
+ contentTypes: health.contentTypes || "all",
6137
+ connected: true,
6138
+ nodeId: health.nodeId
6139
+ });
6140
+ console.log("[ByteCave] \u2713 Discovered peer via HTTP relay:", health.nodeId || peer.peerId.slice(0, 12));
6141
+ }
6142
+ }
6143
+ } catch (err) {
6144
+ console.warn("[ByteCave] Failed to process peer from HTTP relay:", err.message);
6145
+ }
6146
+ }
6147
+ }
6148
+ } catch (err) {
6149
+ console.warn("[ByteCave] HTTP relay discovery failed, falling back to P2P directory:", err.message);
6150
+ }
6151
+ }
6152
+ console.log("[ByteCave] Querying relay for peer directory via P2P...");
6085
6153
  for (const relayAddr of bootstrapPeers) {
6086
6154
  try {
6087
6155
  const parts = relayAddr.split("/p2p/");
@@ -6164,20 +6232,13 @@ var ByteCaveClient = class {
6164
6232
  console.warn("[ByteCave] Cannot refresh - node not initialized");
6165
6233
  return;
6166
6234
  }
6167
- const bootstrapPeers = [
6168
- ...this.config.directNodeAddrs || [],
6169
- ...this.config.relayPeers || []
6170
- ];
6171
- console.log("[ByteCave] Refreshing peer directory from relays...");
6172
- for (const relayAddr of bootstrapPeers) {
6235
+ console.log("[ByteCave] Refreshing peer directory from relay HTTP endpoint...");
6236
+ if (this.relayDiscovery) {
6173
6237
  try {
6174
- const parts = relayAddr.split("/p2p/");
6175
- if (parts.length < 2) continue;
6176
- const relayPeerId = parts[parts.length - 1];
6177
- const directory = await p2pProtocolClient.getPeerDirectoryFromRelay(relayPeerId);
6178
- if (directory && directory.peers.length > 0) {
6179
- console.log("[ByteCave] Refresh: Got", directory.peers.length, "peers from relay directory");
6180
- for (const peer of directory.peers) {
6238
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
6239
+ if (relayPeers.length > 0) {
6240
+ console.log("[ByteCave] Refresh: Got", relayPeers.length, "peers from relay HTTP endpoint");
6241
+ for (const peer of relayPeers) {
6181
6242
  const isConnected = this.node.getPeers().some((p) => p.toString() === peer.peerId);
6182
6243
  const knownPeer = this.knownPeers.get(peer.peerId);
6183
6244
  if (!isConnected || !knownPeer) {
@@ -6215,7 +6276,6 @@ var ByteCaveClient = class {
6215
6276
  console.debug("[ByteCave] Refresh: Peer already connected:", peer.peerId.slice(0, 12) + "...");
6216
6277
  }
6217
6278
  }
6218
- break;
6219
6279
  }
6220
6280
  } catch (err) {
6221
6281
  console.warn("[ByteCave] Refresh: Failed to get directory from relay:", err.message);
@@ -6236,10 +6296,7 @@ var ByteCaveClient = class {
6236
6296
  return { success: false, error: "P2P node not initialized" };
6237
6297
  }
6238
6298
  const allPeers = this.node.getPeers();
6239
- const relayPeerIds = new Set(
6240
- this.config.relayPeers?.map((addr) => addr.split("/p2p/").pop()) || []
6241
- );
6242
- const connectedPeerIds = allPeers.map((p) => p.toString()).filter((peerId) => !relayPeerIds.has(peerId));
6299
+ const connectedPeerIds = allPeers.map((p) => p.toString());
6243
6300
  console.log("[ByteCave] Store - connected storage peers:", connectedPeerIds.length);
6244
6301
  console.log("[ByteCave] Store - knownPeers with registration info:", this.knownPeers.size);
6245
6302
  if (connectedPeerIds.length === 0) {
@@ -6602,11 +6659,11 @@ Nonce: ${nonce}`;
6602
6659
  * Check if a nodeId is registered in the on-chain registry
6603
6660
  */
6604
6661
  async checkNodeRegistration(nodeId) {
6605
- if (!this.discovery) {
6662
+ if (!this.contractDiscovery) {
6606
6663
  return true;
6607
6664
  }
6608
6665
  try {
6609
- const registeredNodes = await this.discovery.getActiveNodes();
6666
+ const registeredNodes = await this.contractDiscovery.getActiveNodes();
6610
6667
  return registeredNodes.some((node) => node.nodeId === nodeId);
6611
6668
  } catch (error) {
6612
6669
  console.warn("[ByteCave] Failed to check node registration:", error);
@@ -7261,6 +7318,7 @@ var TEST_EXPORT = "ByteCave Browser Package v1.0.0";
7261
7318
  HashdAudio,
7262
7319
  HashdImage,
7263
7320
  HashdVideo,
7321
+ RelayDiscovery,
7264
7322
  TEST_EXPORT,
7265
7323
  clearHashdCache,
7266
7324
  createHashdUrl,
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export declare const TEST_EXPORT = "ByteCave Browser Package v1.0.0";
8
8
  export { ByteCaveClient } from './client.js';
9
- export { ContractDiscovery } from './discovery.js';
9
+ export { ContractDiscovery, RelayDiscovery } from './discovery.js';
10
10
  export { p2pProtocolClient } from './p2p-protocols.js';
11
11
  export { ByteCaveProvider, useByteCaveContext } from './provider.js';
12
12
  export type { P2PHealthResponse, P2PInfoResponse } from './p2p-protocols.js';
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  HashdAudio,
6
6
  HashdImage,
7
7
  HashdVideo,
8
+ RelayDiscovery,
8
9
  p2pProtocolClient,
9
10
  useByteCaveContext,
10
11
  useHashdBatch,
@@ -12,7 +13,7 @@ import {
12
13
  useHashdImage,
13
14
  useHashdMedia,
14
15
  useHashdUrl
15
- } from "./chunk-OJEETLZQ.js";
16
+ } from "./chunk-O56JMOMV.js";
16
17
  import {
17
18
  clearHashdCache,
18
19
  createHashdUrl,
@@ -32,6 +33,7 @@ export {
32
33
  HashdAudio,
33
34
  HashdImage,
34
35
  HashdVideo,
36
+ RelayDiscovery,
35
37
  TEST_EXPORT,
36
38
  clearHashdCache,
37
39
  createHashdUrl,
@@ -8,7 +8,7 @@ import {
8
8
  useHashdImage,
9
9
  useHashdMedia,
10
10
  useHashdUrl
11
- } from "../chunk-OJEETLZQ.js";
11
+ } from "../chunk-O56JMOMV.js";
12
12
  import "../chunk-EEZWRIUI.js";
13
13
  export {
14
14
  HashdAudio,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethashd/bytecave-browser",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
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",
package/src/client.ts CHANGED
@@ -17,7 +17,7 @@ import { multiaddr } from '@multiformats/multiaddr';
17
17
  import { peerIdFromString } from '@libp2p/peer-id';
18
18
  import { fromString, toString } from 'uint8arrays';
19
19
  import { ethers } from 'ethers';
20
- import { ContractDiscovery } from './discovery.js';
20
+ import { ContractDiscovery, RelayDiscovery } from './discovery.js';
21
21
  import { p2pProtocolClient } from './p2p-protocols.js';
22
22
  import { CONTENT_REGISTRY_ABI } from './contracts/ContentRegistry.js';
23
23
  import type {
@@ -34,7 +34,8 @@ const SIGNALING_TOPIC_PREFIX = 'bytecave-signaling-';
34
34
 
35
35
  export class ByteCaveClient {
36
36
  private node: Libp2p | null = null;
37
- private discovery?: ContractDiscovery; // Optional - only if contract address provided
37
+ private contractDiscovery?: ContractDiscovery; // Optional - only if contract address provided
38
+ private relayDiscovery?: RelayDiscovery; // Optional - for fast peer discovery via relay
38
39
  private config: ByteCaveConfig;
39
40
  private knownPeers: Map<string, PeerInfo> = new Map();
40
41
  private connectionState: ConnectionState = 'disconnected';
@@ -46,9 +47,18 @@ export class ByteCaveClient {
46
47
  connectionTimeout: 30000,
47
48
  ...config
48
49
  };
49
- // Only initialize contract discovery if contract address is provided
50
+
51
+ // Initialize relay discovery using HTTP URLs from relayPeers
52
+ if (config.relayPeers && config.relayPeers.length > 0) {
53
+ // Use first relay HTTP URL for peer discovery
54
+ const relayHttpUrl = config.relayPeers[0];
55
+ this.relayDiscovery = new RelayDiscovery(relayHttpUrl);
56
+ console.log('[ByteCave] Using relay HTTP URL for peer discovery:', relayHttpUrl);
57
+ }
58
+
59
+ // Initialize contract discovery if contract address is provided
50
60
  if (config.vaultNodeRegistryAddress && config.rpcUrl) {
51
- this.discovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
61
+ this.contractDiscovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
52
62
  }
53
63
  }
54
64
 
@@ -73,14 +83,8 @@ export class ByteCaveClient {
73
83
  bootstrapPeers.push(...this.config.directNodeAddrs);
74
84
  }
75
85
 
76
- // Use relay peers for fallback (circuit relay connections)
77
- if (this.config.relayPeers && this.config.relayPeers.length > 0) {
78
- console.log('[ByteCave] Using relay peers as fallback:', this.config.relayPeers);
79
- bootstrapPeers.push(...this.config.relayPeers);
80
- }
81
-
82
86
  if (bootstrapPeers.length === 0) {
83
- console.warn('[ByteCave] No peers configured - will rely on contract discovery only');
87
+ console.warn('[ByteCave] No direct node addresses configured - will rely on relay peer discovery');
84
88
  }
85
89
 
86
90
  console.log('[ByteCave] Bootstrap peers:', bootstrapPeers);
@@ -144,8 +148,58 @@ export class ByteCaveClient {
144
148
  // Initialize P2P protocol client with the libp2p node
145
149
  p2pProtocolClient.setNode(this.node);
146
150
 
147
- // Fast discovery: Query relay for peer directory
148
- console.log('[ByteCave] Querying relay for peer directory...');
151
+ // FASTEST discovery: Query relay HTTP endpoint for connected peers
152
+ if (this.relayDiscovery) {
153
+ console.log('[ByteCave] Querying relay HTTP endpoint for instant peer list...');
154
+ try {
155
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
156
+ if (relayPeers.length > 0) {
157
+ console.log('[ByteCave] Got', relayPeers.length, 'peers from relay HTTP endpoint');
158
+
159
+ // Dial each peer using their relay circuit multiaddrs
160
+ for (const peer of relayPeers) {
161
+ try {
162
+ console.log('[ByteCave] Dialing peer:', peer.peerId.slice(0, 12) + '...');
163
+
164
+ let connected = false;
165
+ for (const addr of peer.multiaddrs) {
166
+ try {
167
+ const ma = multiaddr(addr);
168
+ await this.node.dial(ma as any);
169
+ connected = true;
170
+ console.log('[ByteCave] ✓ Connected via relay circuit');
171
+ break;
172
+ } catch (dialErr: any) {
173
+ console.warn('[ByteCave] Failed to dial:', dialErr.message);
174
+ }
175
+ }
176
+
177
+ if (connected) {
178
+ // Fetch health data
179
+ const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
180
+ if (health) {
181
+ this.knownPeers.set(peer.peerId, {
182
+ peerId: peer.peerId,
183
+ publicKey: health.publicKey || '',
184
+ contentTypes: health.contentTypes || 'all',
185
+ connected: true,
186
+ nodeId: health.nodeId
187
+ });
188
+ console.log('[ByteCave] ✓ Discovered peer via HTTP relay:', health.nodeId || peer.peerId.slice(0, 12));
189
+ }
190
+ }
191
+ } catch (err: any) {
192
+ console.warn('[ByteCave] Failed to process peer from HTTP relay:', err.message);
193
+ }
194
+ }
195
+ }
196
+ } catch (err: any) {
197
+ console.warn('[ByteCave] HTTP relay discovery failed, falling back to P2P directory:', err.message);
198
+ }
199
+ }
200
+
201
+ // Fallback: Query relay for peer directory via P2P protocol
202
+ console.log('[ByteCave] Querying relay for peer directory via P2P...');
149
203
  for (const relayAddr of bootstrapPeers) {
150
204
  try {
151
205
  // Extract relay peer ID from multiaddr
@@ -244,33 +298,24 @@ export class ByteCaveClient {
244
298
  return;
245
299
  }
246
300
 
247
- const bootstrapPeers = [
248
- ...(this.config.directNodeAddrs || []),
249
- ...(this.config.relayPeers || [])
250
- ];
251
-
252
- console.log('[ByteCave] Refreshing peer directory from relays...');
301
+ console.log('[ByteCave] Refreshing peer directory from relay HTTP endpoint...');
253
302
 
254
- for (const relayAddr of bootstrapPeers) {
303
+ // Use HTTP relay discovery for instant refresh
304
+ if (this.relayDiscovery) {
255
305
  try {
256
- // Extract relay peer ID from multiaddr
257
- const parts = relayAddr.split('/p2p/');
258
- if (parts.length < 2) continue;
259
- const relayPeerId = parts[parts.length - 1];
260
-
261
- const directory = await p2pProtocolClient.getPeerDirectoryFromRelay(relayPeerId);
262
- if (directory && directory.peers.length > 0) {
263
- console.log('[ByteCave] Refresh: Got', directory.peers.length, 'peers from relay directory');
306
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
307
+ if (relayPeers.length > 0) {
308
+ console.log('[ByteCave] Refresh: Got', relayPeers.length, 'peers from relay HTTP endpoint');
264
309
 
265
310
  // Check each peer and reconnect if disconnected
266
- for (const peer of directory.peers) {
311
+ for (const peer of relayPeers) {
267
312
  const isConnected = this.node.getPeers().some(p => p.toString() === peer.peerId);
268
313
  const knownPeer = this.knownPeers.get(peer.peerId);
269
314
 
270
315
  if (!isConnected || !knownPeer) {
271
316
  console.log('[ByteCave] Refresh: Reconnecting to peer:', peer.peerId.slice(0, 12) + '...');
272
317
 
273
- // Try to dial using circuit relay multiaddr
318
+ // Try to dial using relay circuit multiaddr
274
319
  let connected = false;
275
320
  for (const addr of peer.multiaddrs) {
276
321
  try {
@@ -306,8 +351,6 @@ export class ByteCaveClient {
306
351
  console.debug('[ByteCave] Refresh: Peer already connected:', peer.peerId.slice(0, 12) + '...');
307
352
  }
308
353
  }
309
-
310
- break; // Successfully refreshed from one relay
311
354
  }
312
355
  } catch (err: any) {
313
356
  console.warn('[ByteCave] Refresh: Failed to get directory from relay:', err.message);
@@ -330,15 +373,9 @@ export class ByteCaveClient {
330
373
  return { success: false, error: 'P2P node not initialized' };
331
374
  }
332
375
 
333
- // Get all connected peers (excluding relay)
376
+ // Get all connected peers
334
377
  const allPeers = this.node.getPeers();
335
- const relayPeerIds = new Set(
336
- this.config.relayPeers?.map(addr => addr.split('/p2p/').pop()) || []
337
- );
338
-
339
- const connectedPeerIds = allPeers
340
- .map(p => p.toString())
341
- .filter(peerId => !relayPeerIds.has(peerId));
378
+ const connectedPeerIds = allPeers.map(p => p.toString());
342
379
 
343
380
  console.log('[ByteCave] Store - connected storage peers:', connectedPeerIds.length);
344
381
  console.log('[ByteCave] Store - knownPeers with registration info:', this.knownPeers.size);
@@ -807,14 +844,14 @@ Nonce: ${nonce}`;
807
844
  * Check if a nodeId is registered in the on-chain registry
808
845
  */
809
846
  private async checkNodeRegistration(nodeId: string): Promise<boolean> {
810
- if (!this.discovery) {
847
+ if (!this.contractDiscovery) {
811
848
  // No contract configured - skip registration check
812
849
  return true;
813
850
  }
814
851
 
815
852
  try {
816
- const registeredNodes = await this.discovery.getActiveNodes();
817
- return registeredNodes.some(node => node.nodeId === nodeId);
853
+ const registeredNodes = await this.contractDiscovery.getActiveNodes();
854
+ return registeredNodes.some((node: any) => node.nodeId === nodeId);
818
855
  } catch (error) {
819
856
  console.warn('[ByteCave] Failed to check node registration:', error);
820
857
  return false;
package/src/discovery.ts CHANGED
@@ -1,13 +1,45 @@
1
1
  /**
2
- * Contract-based peer discovery
2
+ * Peer discovery for ByteCave browser clients
3
3
  *
4
- * Reads registered nodes from the VaultNodeRegistry smart contract
5
- * to bootstrap P2P connections without any central server.
4
+ * Supports two discovery methods:
5
+ * 1. RelayDiscovery - Fast: Query relay's /peers endpoint for instant peer list
6
+ * 2. ContractDiscovery - Slow: Read from VaultNodeRegistry smart contract
6
7
  */
7
8
 
8
9
  import { ethers } from 'ethers';
9
10
  import type { NodeRegistryEntry } from './types.js';
10
11
 
12
+ /**
13
+ * Relay-based peer discovery (FAST)
14
+ * Queries the relay's HTTP endpoint for currently connected storage nodes
15
+ */
16
+ export class RelayDiscovery {
17
+ private relayHttpUrl: string;
18
+
19
+ constructor(relayHttpUrl: string) {
20
+ this.relayHttpUrl = relayHttpUrl;
21
+ }
22
+
23
+ /**
24
+ * Get currently connected storage nodes from relay
25
+ * Returns peer IDs and relay circuit multiaddrs
26
+ */
27
+ async getConnectedPeers(): Promise<Array<{ peerId: string; multiaddrs: string[] }>> {
28
+ try {
29
+ const response = await fetch(`${this.relayHttpUrl}/peers`);
30
+ if (!response.ok) {
31
+ throw new Error(`Relay returned ${response.status}`);
32
+ }
33
+ const peers = await response.json();
34
+ console.log('[RelayDiscovery] Found connected peers:', peers.length);
35
+ return peers;
36
+ } catch (error) {
37
+ console.warn('[RelayDiscovery] Failed to fetch peers from relay:', error);
38
+ return [];
39
+ }
40
+ }
41
+ }
42
+
11
43
  const VAULT_REGISTRY_ABI = [
12
44
  'function getActiveNodes() external view returns (bytes32[] memory)',
13
45
  'function getNode(bytes32 nodeId) external view returns (tuple(address owner, bytes publicKey, string url, bytes32 metadataHash, uint256 registeredAt, bool active))',
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  export const TEST_EXPORT = "ByteCave Browser Package v1.0.0";
10
10
 
11
11
  export { ByteCaveClient } from './client.js';
12
- export { ContractDiscovery } from './discovery.js';
12
+ export { ContractDiscovery, RelayDiscovery } from './discovery.js';
13
13
  export { p2pProtocolClient } from './p2p-protocols.js';
14
14
 
15
15
  // Provider exports
package/src/types.ts CHANGED
@@ -8,7 +8,7 @@ export interface ByteCaveConfig {
8
8
  rpcUrl?: string; // Optional - required if vaultNodeRegistryAddress is provided
9
9
  appId: string; // Application identifier for storage authorization
10
10
  directNodeAddrs?: string[]; // Direct node multiaddrs for WebRTC connections (no relay)
11
- relayPeers?: string[]; // Relay node multiaddrs for circuit relay fallback
11
+ relayPeers?: string[]; // Relay HTTP URLs (e.g., http://localhost:9090) for peer discovery
12
12
  maxPeers?: number;
13
13
  connectionTimeout?: number;
14
14
  }