@gethashd/bytecave-browser 1.0.2 → 1.0.4

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,12 @@ var ByteCaveClient = class {
5956
5979
  connectionTimeout: 3e4,
5957
5980
  ...config
5958
5981
  };
5982
+ if (config.relayHttpUrl) {
5983
+ this.relayDiscovery = new RelayDiscovery(config.relayHttpUrl);
5984
+ console.log("[ByteCave] Using relay HTTP URL for peer discovery:", config.relayHttpUrl);
5985
+ }
5959
5986
  if (config.vaultNodeRegistryAddress && config.rpcUrl) {
5960
- this.discovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
5987
+ this.contractDiscovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
5961
5988
  }
5962
5989
  }
5963
5990
  /**
@@ -5977,7 +6004,7 @@ var ByteCaveClient = class {
5977
6004
  bootstrapPeers.push(...this.config.directNodeAddrs);
5978
6005
  }
5979
6006
  if (this.config.relayPeers && this.config.relayPeers.length > 0) {
5980
- console.log("[ByteCave] Using relay peers as fallback:", this.config.relayPeers);
6007
+ console.log("[ByteCave] Using relay peers for circuit relay:", this.config.relayPeers);
5981
6008
  bootstrapPeers.push(...this.config.relayPeers);
5982
6009
  }
5983
6010
  if (bootstrapPeers.length === 0) {
@@ -6029,7 +6056,50 @@ var ByteCaveClient = class {
6029
6056
  const connectedPeers = this.node.getPeers();
6030
6057
  console.log("[ByteCave] Connected peers after relay dial:", connectedPeers.length, connectedPeers.map((p) => p.toString()));
6031
6058
  p2pProtocolClient.setNode(this.node);
6032
- console.log("[ByteCave] Querying relay for peer directory...");
6059
+ if (this.relayDiscovery) {
6060
+ console.log("[ByteCave] Querying relay HTTP endpoint for instant peer list...");
6061
+ try {
6062
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
6063
+ if (relayPeers.length > 0) {
6064
+ console.log("[ByteCave] Got", relayPeers.length, "peers from relay HTTP endpoint");
6065
+ for (const peer of relayPeers) {
6066
+ try {
6067
+ console.log("[ByteCave] Dialing peer:", peer.peerId.slice(0, 12) + "...");
6068
+ let connected = false;
6069
+ for (const addr of peer.multiaddrs) {
6070
+ try {
6071
+ const ma = multiaddr(addr);
6072
+ await this.node.dial(ma);
6073
+ connected = true;
6074
+ console.log("[ByteCave] \u2713 Connected via relay circuit");
6075
+ break;
6076
+ } catch (dialErr) {
6077
+ console.warn("[ByteCave] Failed to dial:", dialErr.message);
6078
+ }
6079
+ }
6080
+ if (connected) {
6081
+ const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
6082
+ if (health) {
6083
+ this.knownPeers.set(peer.peerId, {
6084
+ peerId: peer.peerId,
6085
+ publicKey: health.publicKey || "",
6086
+ contentTypes: health.contentTypes || "all",
6087
+ connected: true,
6088
+ nodeId: health.nodeId
6089
+ });
6090
+ console.log("[ByteCave] \u2713 Discovered peer via HTTP relay:", health.nodeId || peer.peerId.slice(0, 12));
6091
+ }
6092
+ }
6093
+ } catch (err) {
6094
+ console.warn("[ByteCave] Failed to process peer from HTTP relay:", err.message);
6095
+ }
6096
+ }
6097
+ }
6098
+ } catch (err) {
6099
+ console.warn("[ByteCave] HTTP relay discovery failed, falling back to P2P directory:", err.message);
6100
+ }
6101
+ }
6102
+ console.log("[ByteCave] Querying relay for peer directory via P2P...");
6033
6103
  for (const relayAddr of bootstrapPeers) {
6034
6104
  try {
6035
6105
  const parts = relayAddr.split("/p2p/");
@@ -6112,20 +6182,13 @@ var ByteCaveClient = class {
6112
6182
  console.warn("[ByteCave] Cannot refresh - node not initialized");
6113
6183
  return;
6114
6184
  }
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) {
6185
+ console.log("[ByteCave] Refreshing peer directory from relay HTTP endpoint...");
6186
+ if (this.relayDiscovery) {
6121
6187
  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) {
6188
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
6189
+ if (relayPeers.length > 0) {
6190
+ console.log("[ByteCave] Refresh: Got", relayPeers.length, "peers from relay HTTP endpoint");
6191
+ for (const peer of relayPeers) {
6129
6192
  const isConnected = this.node.getPeers().some((p) => p.toString() === peer.peerId);
6130
6193
  const knownPeer = this.knownPeers.get(peer.peerId);
6131
6194
  if (!isConnected || !knownPeer) {
@@ -6163,7 +6226,6 @@ var ByteCaveClient = class {
6163
6226
  console.debug("[ByteCave] Refresh: Peer already connected:", peer.peerId.slice(0, 12) + "...");
6164
6227
  }
6165
6228
  }
6166
- break;
6167
6229
  }
6168
6230
  } catch (err) {
6169
6231
  console.warn("[ByteCave] Refresh: Failed to get directory from relay:", err.message);
@@ -6184,10 +6246,7 @@ var ByteCaveClient = class {
6184
6246
  return { success: false, error: "P2P node not initialized" };
6185
6247
  }
6186
6248
  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));
6249
+ const connectedPeerIds = allPeers.map((p) => p.toString());
6191
6250
  console.log("[ByteCave] Store - connected storage peers:", connectedPeerIds.length);
6192
6251
  console.log("[ByteCave] Store - knownPeers with registration info:", this.knownPeers.size);
6193
6252
  if (connectedPeerIds.length === 0) {
@@ -6550,11 +6609,11 @@ Nonce: ${nonce}`;
6550
6609
  * Check if a nodeId is registered in the on-chain registry
6551
6610
  */
6552
6611
  async checkNodeRegistration(nodeId) {
6553
- if (!this.discovery) {
6612
+ if (!this.contractDiscovery) {
6554
6613
  return true;
6555
6614
  }
6556
6615
  try {
6557
- const registeredNodes = await this.discovery.getActiveNodes();
6616
+ const registeredNodes = await this.contractDiscovery.getActiveNodes();
6558
6617
  return registeredNodes.some((node) => node.nodeId === nodeId);
6559
6618
  } catch (error) {
6560
6619
  console.warn("[ByteCave] Failed to check node registration:", error);
@@ -6615,6 +6674,7 @@ function ByteCaveProvider({
6615
6674
  rpcUrl,
6616
6675
  appId,
6617
6676
  relayPeers = [],
6677
+ relayHttpUrl,
6618
6678
  directNodeAddrs = []
6619
6679
  }) {
6620
6680
  const [connectionState, setConnectionState] = useState("disconnected");
@@ -6657,6 +6717,7 @@ function ByteCaveProvider({
6657
6717
  appId,
6658
6718
  directNodeAddrs,
6659
6719
  relayPeers,
6720
+ relayHttpUrl,
6660
6721
  maxPeers: 10,
6661
6722
  connectionTimeout: 3e4
6662
6723
  });
@@ -7058,6 +7119,7 @@ function useHashdUrl(hashdUrl) {
7058
7119
  }
7059
7120
 
7060
7121
  export {
7122
+ RelayDiscovery,
7061
7123
  ContractDiscovery,
7062
7124
  p2pProtocolClient,
7063
7125
  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,12 @@ var ByteCaveClient = class {
6008
6032
  connectionTimeout: 3e4,
6009
6033
  ...config
6010
6034
  };
6035
+ if (config.relayHttpUrl) {
6036
+ this.relayDiscovery = new RelayDiscovery(config.relayHttpUrl);
6037
+ console.log("[ByteCave] Using relay HTTP URL for peer discovery:", config.relayHttpUrl);
6038
+ }
6011
6039
  if (config.vaultNodeRegistryAddress && config.rpcUrl) {
6012
- this.discovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
6040
+ this.contractDiscovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
6013
6041
  }
6014
6042
  }
6015
6043
  /**
@@ -6029,7 +6057,7 @@ var ByteCaveClient = class {
6029
6057
  bootstrapPeers.push(...this.config.directNodeAddrs);
6030
6058
  }
6031
6059
  if (this.config.relayPeers && this.config.relayPeers.length > 0) {
6032
- console.log("[ByteCave] Using relay peers as fallback:", this.config.relayPeers);
6060
+ console.log("[ByteCave] Using relay peers for circuit relay:", this.config.relayPeers);
6033
6061
  bootstrapPeers.push(...this.config.relayPeers);
6034
6062
  }
6035
6063
  if (bootstrapPeers.length === 0) {
@@ -6081,7 +6109,50 @@ var ByteCaveClient = class {
6081
6109
  const connectedPeers = this.node.getPeers();
6082
6110
  console.log("[ByteCave] Connected peers after relay dial:", connectedPeers.length, connectedPeers.map((p) => p.toString()));
6083
6111
  p2pProtocolClient.setNode(this.node);
6084
- console.log("[ByteCave] Querying relay for peer directory...");
6112
+ if (this.relayDiscovery) {
6113
+ console.log("[ByteCave] Querying relay HTTP endpoint for instant peer list...");
6114
+ try {
6115
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
6116
+ if (relayPeers.length > 0) {
6117
+ console.log("[ByteCave] Got", relayPeers.length, "peers from relay HTTP endpoint");
6118
+ for (const peer of relayPeers) {
6119
+ try {
6120
+ console.log("[ByteCave] Dialing peer:", peer.peerId.slice(0, 12) + "...");
6121
+ let connected = false;
6122
+ for (const addr of peer.multiaddrs) {
6123
+ try {
6124
+ const ma = (0, import_multiaddr.multiaddr)(addr);
6125
+ await this.node.dial(ma);
6126
+ connected = true;
6127
+ console.log("[ByteCave] \u2713 Connected via relay circuit");
6128
+ break;
6129
+ } catch (dialErr) {
6130
+ console.warn("[ByteCave] Failed to dial:", dialErr.message);
6131
+ }
6132
+ }
6133
+ if (connected) {
6134
+ const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
6135
+ if (health) {
6136
+ this.knownPeers.set(peer.peerId, {
6137
+ peerId: peer.peerId,
6138
+ publicKey: health.publicKey || "",
6139
+ contentTypes: health.contentTypes || "all",
6140
+ connected: true,
6141
+ nodeId: health.nodeId
6142
+ });
6143
+ console.log("[ByteCave] \u2713 Discovered peer via HTTP relay:", health.nodeId || peer.peerId.slice(0, 12));
6144
+ }
6145
+ }
6146
+ } catch (err) {
6147
+ console.warn("[ByteCave] Failed to process peer from HTTP relay:", err.message);
6148
+ }
6149
+ }
6150
+ }
6151
+ } catch (err) {
6152
+ console.warn("[ByteCave] HTTP relay discovery failed, falling back to P2P directory:", err.message);
6153
+ }
6154
+ }
6155
+ console.log("[ByteCave] Querying relay for peer directory via P2P...");
6085
6156
  for (const relayAddr of bootstrapPeers) {
6086
6157
  try {
6087
6158
  const parts = relayAddr.split("/p2p/");
@@ -6164,20 +6235,13 @@ var ByteCaveClient = class {
6164
6235
  console.warn("[ByteCave] Cannot refresh - node not initialized");
6165
6236
  return;
6166
6237
  }
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) {
6238
+ console.log("[ByteCave] Refreshing peer directory from relay HTTP endpoint...");
6239
+ if (this.relayDiscovery) {
6173
6240
  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) {
6241
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
6242
+ if (relayPeers.length > 0) {
6243
+ console.log("[ByteCave] Refresh: Got", relayPeers.length, "peers from relay HTTP endpoint");
6244
+ for (const peer of relayPeers) {
6181
6245
  const isConnected = this.node.getPeers().some((p) => p.toString() === peer.peerId);
6182
6246
  const knownPeer = this.knownPeers.get(peer.peerId);
6183
6247
  if (!isConnected || !knownPeer) {
@@ -6215,7 +6279,6 @@ var ByteCaveClient = class {
6215
6279
  console.debug("[ByteCave] Refresh: Peer already connected:", peer.peerId.slice(0, 12) + "...");
6216
6280
  }
6217
6281
  }
6218
- break;
6219
6282
  }
6220
6283
  } catch (err) {
6221
6284
  console.warn("[ByteCave] Refresh: Failed to get directory from relay:", err.message);
@@ -6236,10 +6299,7 @@ var ByteCaveClient = class {
6236
6299
  return { success: false, error: "P2P node not initialized" };
6237
6300
  }
6238
6301
  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));
6302
+ const connectedPeerIds = allPeers.map((p) => p.toString());
6243
6303
  console.log("[ByteCave] Store - connected storage peers:", connectedPeerIds.length);
6244
6304
  console.log("[ByteCave] Store - knownPeers with registration info:", this.knownPeers.size);
6245
6305
  if (connectedPeerIds.length === 0) {
@@ -6602,11 +6662,11 @@ Nonce: ${nonce}`;
6602
6662
  * Check if a nodeId is registered in the on-chain registry
6603
6663
  */
6604
6664
  async checkNodeRegistration(nodeId) {
6605
- if (!this.discovery) {
6665
+ if (!this.contractDiscovery) {
6606
6666
  return true;
6607
6667
  }
6608
6668
  try {
6609
- const registeredNodes = await this.discovery.getActiveNodes();
6669
+ const registeredNodes = await this.contractDiscovery.getActiveNodes();
6610
6670
  return registeredNodes.some((node) => node.nodeId === nodeId);
6611
6671
  } catch (error) {
6612
6672
  console.warn("[ByteCave] Failed to check node registration:", error);
@@ -6667,6 +6727,7 @@ function ByteCaveProvider({
6667
6727
  rpcUrl,
6668
6728
  appId,
6669
6729
  relayPeers = [],
6730
+ relayHttpUrl,
6670
6731
  directNodeAddrs = []
6671
6732
  }) {
6672
6733
  const [connectionState, setConnectionState] = (0, import_react.useState)("disconnected");
@@ -6709,6 +6770,7 @@ function ByteCaveProvider({
6709
6770
  appId,
6710
6771
  directNodeAddrs,
6711
6772
  relayPeers,
6773
+ relayHttpUrl,
6712
6774
  maxPeers: 10,
6713
6775
  connectionTimeout: 3e4
6714
6776
  });
@@ -7261,6 +7323,7 @@ var TEST_EXPORT = "ByteCave Browser Package v1.0.0";
7261
7323
  HashdAudio,
7262
7324
  HashdImage,
7263
7325
  HashdVideo,
7326
+ RelayDiscovery,
7264
7327
  TEST_EXPORT,
7265
7328
  clearHashdCache,
7266
7329
  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-6R5YCAYU.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,
@@ -52,8 +52,9 @@ interface ByteCaveProviderProps {
52
52
  rpcUrl: string;
53
53
  appId: string;
54
54
  relayPeers?: string[];
55
+ relayHttpUrl?: string;
55
56
  directNodeAddrs?: string[];
56
57
  }
57
- export declare function ByteCaveProvider({ children, vaultNodeRegistryAddress, contentRegistryAddress, rpcUrl, appId, relayPeers, directNodeAddrs }: ByteCaveProviderProps): React.JSX.Element;
58
+ export declare function ByteCaveProvider({ children, vaultNodeRegistryAddress, contentRegistryAddress, rpcUrl, appId, relayPeers, relayHttpUrl, directNodeAddrs }: ByteCaveProviderProps): React.JSX.Element;
58
59
  export declare function useByteCaveContext(): ByteCaveContextValue;
59
60
  export {};
@@ -8,7 +8,7 @@ import {
8
8
  useHashdImage,
9
9
  useHashdMedia,
10
10
  useHashdUrl
11
- } from "../chunk-OJEETLZQ.js";
11
+ } from "../chunk-6R5YCAYU.js";
12
12
  import "../chunk-EEZWRIUI.js";
13
13
  export {
14
14
  HashdAudio,
package/dist/types.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface ByteCaveConfig {
8
8
  appId: string;
9
9
  directNodeAddrs?: string[];
10
10
  relayPeers?: string[];
11
+ relayHttpUrl?: string;
11
12
  maxPeers?: number;
12
13
  connectionTimeout?: number;
13
14
  }
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.4",
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,16 @@ 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 URL if provided
52
+ if (config.relayHttpUrl) {
53
+ this.relayDiscovery = new RelayDiscovery(config.relayHttpUrl);
54
+ console.log('[ByteCave] Using relay HTTP URL for peer discovery:', config.relayHttpUrl);
55
+ }
56
+
57
+ // Initialize contract discovery if contract address is provided
50
58
  if (config.vaultNodeRegistryAddress && config.rpcUrl) {
51
- this.discovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
59
+ this.contractDiscovery = new ContractDiscovery(config.vaultNodeRegistryAddress, config.rpcUrl);
52
60
  }
53
61
  }
54
62
 
@@ -73,9 +81,9 @@ export class ByteCaveClient {
73
81
  bootstrapPeers.push(...this.config.directNodeAddrs);
74
82
  }
75
83
 
76
- // Use relay peers for fallback (circuit relay connections)
84
+ // Add relay peers for circuit relay connections
77
85
  if (this.config.relayPeers && this.config.relayPeers.length > 0) {
78
- console.log('[ByteCave] Using relay peers as fallback:', this.config.relayPeers);
86
+ console.log('[ByteCave] Using relay peers for circuit relay:', this.config.relayPeers);
79
87
  bootstrapPeers.push(...this.config.relayPeers);
80
88
  }
81
89
 
@@ -144,8 +152,58 @@ export class ByteCaveClient {
144
152
  // Initialize P2P protocol client with the libp2p node
145
153
  p2pProtocolClient.setNode(this.node);
146
154
 
147
- // Fast discovery: Query relay for peer directory
148
- console.log('[ByteCave] Querying relay for peer directory...');
155
+ // FASTEST discovery: Query relay HTTP endpoint for connected peers
156
+ if (this.relayDiscovery) {
157
+ console.log('[ByteCave] Querying relay HTTP endpoint for instant peer list...');
158
+ try {
159
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
160
+ if (relayPeers.length > 0) {
161
+ console.log('[ByteCave] Got', relayPeers.length, 'peers from relay HTTP endpoint');
162
+
163
+ // Dial each peer using their relay circuit multiaddrs
164
+ for (const peer of relayPeers) {
165
+ try {
166
+ console.log('[ByteCave] Dialing peer:', peer.peerId.slice(0, 12) + '...');
167
+
168
+ let connected = false;
169
+ for (const addr of peer.multiaddrs) {
170
+ try {
171
+ const ma = multiaddr(addr);
172
+ await this.node.dial(ma as any);
173
+ connected = true;
174
+ console.log('[ByteCave] ✓ Connected via relay circuit');
175
+ break;
176
+ } catch (dialErr: any) {
177
+ console.warn('[ByteCave] Failed to dial:', dialErr.message);
178
+ }
179
+ }
180
+
181
+ if (connected) {
182
+ // Fetch health data
183
+ const health = await p2pProtocolClient.getHealthFromPeer(peer.peerId);
184
+ if (health) {
185
+ this.knownPeers.set(peer.peerId, {
186
+ peerId: peer.peerId,
187
+ publicKey: health.publicKey || '',
188
+ contentTypes: health.contentTypes || 'all',
189
+ connected: true,
190
+ nodeId: health.nodeId
191
+ });
192
+ console.log('[ByteCave] ✓ Discovered peer via HTTP relay:', health.nodeId || peer.peerId.slice(0, 12));
193
+ }
194
+ }
195
+ } catch (err: any) {
196
+ console.warn('[ByteCave] Failed to process peer from HTTP relay:', err.message);
197
+ }
198
+ }
199
+ }
200
+ } catch (err: any) {
201
+ console.warn('[ByteCave] HTTP relay discovery failed, falling back to P2P directory:', err.message);
202
+ }
203
+ }
204
+
205
+ // Fallback: Query relay for peer directory via P2P protocol
206
+ console.log('[ByteCave] Querying relay for peer directory via P2P...');
149
207
  for (const relayAddr of bootstrapPeers) {
150
208
  try {
151
209
  // Extract relay peer ID from multiaddr
@@ -244,33 +302,24 @@ export class ByteCaveClient {
244
302
  return;
245
303
  }
246
304
 
247
- const bootstrapPeers = [
248
- ...(this.config.directNodeAddrs || []),
249
- ...(this.config.relayPeers || [])
250
- ];
251
-
252
- console.log('[ByteCave] Refreshing peer directory from relays...');
305
+ console.log('[ByteCave] Refreshing peer directory from relay HTTP endpoint...');
253
306
 
254
- for (const relayAddr of bootstrapPeers) {
307
+ // Use HTTP relay discovery for instant refresh
308
+ if (this.relayDiscovery) {
255
309
  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');
310
+ const relayPeers = await this.relayDiscovery.getConnectedPeers();
311
+ if (relayPeers.length > 0) {
312
+ console.log('[ByteCave] Refresh: Got', relayPeers.length, 'peers from relay HTTP endpoint');
264
313
 
265
314
  // Check each peer and reconnect if disconnected
266
- for (const peer of directory.peers) {
315
+ for (const peer of relayPeers) {
267
316
  const isConnected = this.node.getPeers().some(p => p.toString() === peer.peerId);
268
317
  const knownPeer = this.knownPeers.get(peer.peerId);
269
318
 
270
319
  if (!isConnected || !knownPeer) {
271
320
  console.log('[ByteCave] Refresh: Reconnecting to peer:', peer.peerId.slice(0, 12) + '...');
272
321
 
273
- // Try to dial using circuit relay multiaddr
322
+ // Try to dial using relay circuit multiaddr
274
323
  let connected = false;
275
324
  for (const addr of peer.multiaddrs) {
276
325
  try {
@@ -306,8 +355,6 @@ export class ByteCaveClient {
306
355
  console.debug('[ByteCave] Refresh: Peer already connected:', peer.peerId.slice(0, 12) + '...');
307
356
  }
308
357
  }
309
-
310
- break; // Successfully refreshed from one relay
311
358
  }
312
359
  } catch (err: any) {
313
360
  console.warn('[ByteCave] Refresh: Failed to get directory from relay:', err.message);
@@ -330,15 +377,9 @@ export class ByteCaveClient {
330
377
  return { success: false, error: 'P2P node not initialized' };
331
378
  }
332
379
 
333
- // Get all connected peers (excluding relay)
380
+ // Get all connected peers
334
381
  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));
382
+ const connectedPeerIds = allPeers.map(p => p.toString());
342
383
 
343
384
  console.log('[ByteCave] Store - connected storage peers:', connectedPeerIds.length);
344
385
  console.log('[ByteCave] Store - knownPeers with registration info:', this.knownPeers.size);
@@ -807,14 +848,14 @@ Nonce: ${nonce}`;
807
848
  * Check if a nodeId is registered in the on-chain registry
808
849
  */
809
850
  private async checkNodeRegistration(nodeId: string): Promise<boolean> {
810
- if (!this.discovery) {
851
+ if (!this.contractDiscovery) {
811
852
  // No contract configured - skip registration check
812
853
  return true;
813
854
  }
814
855
 
815
856
  try {
816
- const registeredNodes = await this.discovery.getActiveNodes();
817
- return registeredNodes.some(node => node.nodeId === nodeId);
857
+ const registeredNodes = await this.contractDiscovery.getActiveNodes();
858
+ return registeredNodes.some((node: any) => node.nodeId === nodeId);
818
859
  } catch (error) {
819
860
  console.warn('[ByteCave] Failed to check node registration:', error);
820
861
  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/provider.tsx CHANGED
@@ -55,6 +55,7 @@ interface ByteCaveProviderProps {
55
55
  rpcUrl: string;
56
56
  appId: string;
57
57
  relayPeers?: string[];
58
+ relayHttpUrl?: string;
58
59
  directNodeAddrs?: string[];
59
60
  }
60
61
 
@@ -67,6 +68,7 @@ export function ByteCaveProvider({
67
68
  rpcUrl,
68
69
  appId,
69
70
  relayPeers = [],
71
+ relayHttpUrl,
70
72
  directNodeAddrs = []
71
73
  }: ByteCaveProviderProps) {
72
74
  const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');
@@ -117,6 +119,7 @@ export function ByteCaveProvider({
117
119
  appId,
118
120
  directNodeAddrs,
119
121
  relayPeers,
122
+ relayHttpUrl,
120
123
  maxPeers: 10,
121
124
  connectionTimeout: 30000
122
125
  });
package/src/types.ts CHANGED
@@ -8,7 +8,8 @@ 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 node multiaddrs for circuit relay (e.g., /ip4/127.0.0.1/tcp/4002/ws/p2p/...)
12
+ relayHttpUrl?: string; // Optional - Relay HTTP URL for instant peer discovery (e.g., http://localhost:9090)
12
13
  maxPeers?: number;
13
14
  connectionTimeout?: number;
14
15
  }