@gethashd/bytecave-browser 1.0.29 → 1.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-CTI3NP4T.js → chunk-LPLEL6EN.js} +137 -11
- package/dist/client.d.ts +1 -0
- package/dist/index.cjs +137 -11
- package/dist/index.js +1 -1
- package/dist/provider.d.ts +2 -1
- package/dist/react/index.js +1 -1
- package/dist/storage-websocket.d.ts +30 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/client.ts +46 -15
- package/src/provider.tsx +3 -0
- package/src/storage-websocket.ts +162 -0
- package/src/types.ts +1 -0
|
@@ -6067,6 +6067,105 @@ import { multiaddr } from "@multiformats/multiaddr";
|
|
|
6067
6067
|
import { fromString, toString } from "uint8arrays";
|
|
6068
6068
|
import { ethers as ethers2 } from "ethers";
|
|
6069
6069
|
|
|
6070
|
+
// src/storage-websocket.ts
|
|
6071
|
+
var StorageWebSocketClient = class {
|
|
6072
|
+
constructor(relayUrl) {
|
|
6073
|
+
this.relayUrl = relayUrl;
|
|
6074
|
+
this.ws = null;
|
|
6075
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
6076
|
+
}
|
|
6077
|
+
async connect() {
|
|
6078
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
6079
|
+
return;
|
|
6080
|
+
}
|
|
6081
|
+
return new Promise((resolve, reject) => {
|
|
6082
|
+
this.ws = new WebSocket(this.relayUrl);
|
|
6083
|
+
this.ws.onopen = () => {
|
|
6084
|
+
console.log("[Storage WS] Connected to relay");
|
|
6085
|
+
resolve();
|
|
6086
|
+
};
|
|
6087
|
+
this.ws.onmessage = (event) => {
|
|
6088
|
+
try {
|
|
6089
|
+
const message2 = JSON.parse(event.data);
|
|
6090
|
+
this.handleMessage(message2);
|
|
6091
|
+
} catch (error) {
|
|
6092
|
+
console.error("[Storage WS] Failed to parse message:", error.message);
|
|
6093
|
+
}
|
|
6094
|
+
};
|
|
6095
|
+
this.ws.onerror = (error) => {
|
|
6096
|
+
console.error("[Storage WS] WebSocket error:", error);
|
|
6097
|
+
reject(new Error("WebSocket connection failed"));
|
|
6098
|
+
};
|
|
6099
|
+
this.ws.onclose = () => {
|
|
6100
|
+
console.log("[Storage WS] Connection closed");
|
|
6101
|
+
this.ws = null;
|
|
6102
|
+
for (const [requestId, pending] of this.pendingRequests.entries()) {
|
|
6103
|
+
clearTimeout(pending.timeout);
|
|
6104
|
+
pending.reject(new Error("WebSocket connection closed"));
|
|
6105
|
+
}
|
|
6106
|
+
this.pendingRequests.clear();
|
|
6107
|
+
};
|
|
6108
|
+
});
|
|
6109
|
+
}
|
|
6110
|
+
handleMessage(message2) {
|
|
6111
|
+
if (message2.type === "storage-response") {
|
|
6112
|
+
const pending = this.pendingRequests.get(message2.requestId);
|
|
6113
|
+
if (pending) {
|
|
6114
|
+
clearTimeout(pending.timeout);
|
|
6115
|
+
this.pendingRequests.delete(message2.requestId);
|
|
6116
|
+
if (message2.success && message2.cid) {
|
|
6117
|
+
pending.resolve({ success: true, cid: message2.cid });
|
|
6118
|
+
} else {
|
|
6119
|
+
pending.resolve({ success: false, error: message2.error || "Storage failed" });
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
6124
|
+
async store(options) {
|
|
6125
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
6126
|
+
await this.connect();
|
|
6127
|
+
}
|
|
6128
|
+
const requestId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
6129
|
+
const base64Data = btoa(String.fromCharCode(...options.data));
|
|
6130
|
+
const request = {
|
|
6131
|
+
type: "storage-request",
|
|
6132
|
+
requestId,
|
|
6133
|
+
data: base64Data,
|
|
6134
|
+
contentType: options.contentType,
|
|
6135
|
+
authorization: options.authorization
|
|
6136
|
+
};
|
|
6137
|
+
return new Promise((resolve, reject) => {
|
|
6138
|
+
const timeout = setTimeout(() => {
|
|
6139
|
+
this.pendingRequests.delete(requestId);
|
|
6140
|
+
reject(new Error("Storage request timeout"));
|
|
6141
|
+
}, options.timeout || 3e4);
|
|
6142
|
+
this.pendingRequests.set(requestId, { resolve, reject, timeout });
|
|
6143
|
+
try {
|
|
6144
|
+
this.ws.send(JSON.stringify(request));
|
|
6145
|
+
console.log("[Storage WS] Sent storage request:", requestId);
|
|
6146
|
+
} catch (error) {
|
|
6147
|
+
clearTimeout(timeout);
|
|
6148
|
+
this.pendingRequests.delete(requestId);
|
|
6149
|
+
reject(error);
|
|
6150
|
+
}
|
|
6151
|
+
});
|
|
6152
|
+
}
|
|
6153
|
+
disconnect() {
|
|
6154
|
+
if (this.ws) {
|
|
6155
|
+
this.ws.close();
|
|
6156
|
+
this.ws = null;
|
|
6157
|
+
}
|
|
6158
|
+
for (const [requestId, pending] of this.pendingRequests.entries()) {
|
|
6159
|
+
clearTimeout(pending.timeout);
|
|
6160
|
+
pending.reject(new Error("Client disconnected"));
|
|
6161
|
+
}
|
|
6162
|
+
this.pendingRequests.clear();
|
|
6163
|
+
}
|
|
6164
|
+
isConnected() {
|
|
6165
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
6166
|
+
}
|
|
6167
|
+
};
|
|
6168
|
+
|
|
6070
6169
|
// src/contracts/ContentRegistry.ts
|
|
6071
6170
|
var CONTENT_REGISTRY_ABI = [
|
|
6072
6171
|
"function registerContent(bytes32 cid, address owner, bytes32 appId) external",
|
|
@@ -6449,6 +6548,38 @@ Nonce: ${nonce}`;
|
|
|
6449
6548
|
console.warn("[ByteCave] Failed to create authorization:", err.message);
|
|
6450
6549
|
}
|
|
6451
6550
|
}
|
|
6551
|
+
if (this.config.relayWsUrl) {
|
|
6552
|
+
console.log("[ByteCave] Attempting WebSocket storage via relay");
|
|
6553
|
+
try {
|
|
6554
|
+
if (!this.storageWsClient) {
|
|
6555
|
+
this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
|
|
6556
|
+
}
|
|
6557
|
+
const wsAuth = authorization ? {
|
|
6558
|
+
signature: authorization.signature,
|
|
6559
|
+
address: authorization.sender,
|
|
6560
|
+
timestamp: authorization.timestamp,
|
|
6561
|
+
nonce: authorization.nonce
|
|
6562
|
+
} : void 0;
|
|
6563
|
+
const result = await this.storageWsClient.store({
|
|
6564
|
+
data: dataArray,
|
|
6565
|
+
contentType: mimeType || "application/octet-stream",
|
|
6566
|
+
authorization: wsAuth,
|
|
6567
|
+
timeout: 3e4
|
|
6568
|
+
});
|
|
6569
|
+
if (result.success && result.cid) {
|
|
6570
|
+
console.log("[ByteCave] \u2713 WebSocket storage successful:", result.cid);
|
|
6571
|
+
return {
|
|
6572
|
+
success: true,
|
|
6573
|
+
cid: result.cid,
|
|
6574
|
+
peerId: "relay-ws"
|
|
6575
|
+
};
|
|
6576
|
+
}
|
|
6577
|
+
console.warn("[ByteCave] WebSocket storage failed:", result.error);
|
|
6578
|
+
} catch (err) {
|
|
6579
|
+
console.warn("[ByteCave] WebSocket storage exception:", err.message);
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
console.log("[ByteCave] Falling back to P2P storage");
|
|
6452
6583
|
const errors = [];
|
|
6453
6584
|
for (const peerId of storagePeerIds) {
|
|
6454
6585
|
console.log("[ByteCave] Attempting P2P store to peer:", peerId.slice(0, 12) + "...");
|
|
@@ -6478,8 +6609,8 @@ Nonce: ${nonce}`;
|
|
|
6478
6609
|
errors.push(errorMsg);
|
|
6479
6610
|
}
|
|
6480
6611
|
}
|
|
6481
|
-
console.error("[ByteCave] All storage
|
|
6482
|
-
return { success: false, error: `All storage
|
|
6612
|
+
console.error("[ByteCave] All storage methods failed. Errors:", errors);
|
|
6613
|
+
return { success: false, error: `All storage methods failed: ${errors.join("; ")}` };
|
|
6483
6614
|
}
|
|
6484
6615
|
/**
|
|
6485
6616
|
* Retrieve ciphertext from a node via P2P only (no HTTP fallback)
|
|
@@ -6647,16 +6778,9 @@ Nonce: ${nonce}`;
|
|
|
6647
6778
|
const peerInfo = this.knownPeers.get(peerId);
|
|
6648
6779
|
const relayAddrs = peerInfo?.relayAddrs;
|
|
6649
6780
|
if (relayAddrs && relayAddrs.length > 0 && this.node) {
|
|
6650
|
-
|
|
6651
|
-
const plainRelayAddrs = relayAddrs.filter((addr) => !addr.includes("/webrtc"));
|
|
6652
|
-
const addrToUse = webrtcRelayAddrs.length > 0 ? webrtcRelayAddrs[0] : plainRelayAddrs[0];
|
|
6653
|
-
console.log("[ByteCave] Dialing peer through relay:", peerId.slice(0, 12), {
|
|
6654
|
-
webrtcAvailable: webrtcRelayAddrs.length,
|
|
6655
|
-
plainAvailable: plainRelayAddrs.length,
|
|
6656
|
-
using: addrToUse
|
|
6657
|
-
});
|
|
6781
|
+
console.log("[ByteCave] Dialing peer through relay:", peerId.slice(0, 12), relayAddrs[0]);
|
|
6658
6782
|
try {
|
|
6659
|
-
const ma = multiaddr(
|
|
6783
|
+
const ma = multiaddr(relayAddrs[0]);
|
|
6660
6784
|
await this.node.dial(ma);
|
|
6661
6785
|
console.log("[ByteCave] Successfully dialed peer through relay");
|
|
6662
6786
|
} catch (dialError) {
|
|
@@ -6836,6 +6960,7 @@ function ByteCaveProvider({
|
|
|
6836
6960
|
appId,
|
|
6837
6961
|
relayPeers = [],
|
|
6838
6962
|
relayHttpUrl,
|
|
6963
|
+
relayWsUrl,
|
|
6839
6964
|
directNodeAddrs = []
|
|
6840
6965
|
}) {
|
|
6841
6966
|
const [connectionState, setConnectionState] = useState("disconnected");
|
|
@@ -6879,6 +7004,7 @@ function ByteCaveProvider({
|
|
|
6879
7004
|
directNodeAddrs,
|
|
6880
7005
|
relayPeers,
|
|
6881
7006
|
relayHttpUrl,
|
|
7007
|
+
relayWsUrl,
|
|
6882
7008
|
maxPeers: 10,
|
|
6883
7009
|
connectionTimeout: 3e4
|
|
6884
7010
|
});
|
package/dist/client.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -6120,6 +6120,105 @@ var P2PProtocolClient = class {
|
|
|
6120
6120
|
};
|
|
6121
6121
|
var p2pProtocolClient = new P2PProtocolClient();
|
|
6122
6122
|
|
|
6123
|
+
// src/storage-websocket.ts
|
|
6124
|
+
var StorageWebSocketClient = class {
|
|
6125
|
+
constructor(relayUrl) {
|
|
6126
|
+
this.relayUrl = relayUrl;
|
|
6127
|
+
this.ws = null;
|
|
6128
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
6129
|
+
}
|
|
6130
|
+
async connect() {
|
|
6131
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
6132
|
+
return;
|
|
6133
|
+
}
|
|
6134
|
+
return new Promise((resolve, reject) => {
|
|
6135
|
+
this.ws = new WebSocket(this.relayUrl);
|
|
6136
|
+
this.ws.onopen = () => {
|
|
6137
|
+
console.log("[Storage WS] Connected to relay");
|
|
6138
|
+
resolve();
|
|
6139
|
+
};
|
|
6140
|
+
this.ws.onmessage = (event) => {
|
|
6141
|
+
try {
|
|
6142
|
+
const message2 = JSON.parse(event.data);
|
|
6143
|
+
this.handleMessage(message2);
|
|
6144
|
+
} catch (error) {
|
|
6145
|
+
console.error("[Storage WS] Failed to parse message:", error.message);
|
|
6146
|
+
}
|
|
6147
|
+
};
|
|
6148
|
+
this.ws.onerror = (error) => {
|
|
6149
|
+
console.error("[Storage WS] WebSocket error:", error);
|
|
6150
|
+
reject(new Error("WebSocket connection failed"));
|
|
6151
|
+
};
|
|
6152
|
+
this.ws.onclose = () => {
|
|
6153
|
+
console.log("[Storage WS] Connection closed");
|
|
6154
|
+
this.ws = null;
|
|
6155
|
+
for (const [requestId, pending] of this.pendingRequests.entries()) {
|
|
6156
|
+
clearTimeout(pending.timeout);
|
|
6157
|
+
pending.reject(new Error("WebSocket connection closed"));
|
|
6158
|
+
}
|
|
6159
|
+
this.pendingRequests.clear();
|
|
6160
|
+
};
|
|
6161
|
+
});
|
|
6162
|
+
}
|
|
6163
|
+
handleMessage(message2) {
|
|
6164
|
+
if (message2.type === "storage-response") {
|
|
6165
|
+
const pending = this.pendingRequests.get(message2.requestId);
|
|
6166
|
+
if (pending) {
|
|
6167
|
+
clearTimeout(pending.timeout);
|
|
6168
|
+
this.pendingRequests.delete(message2.requestId);
|
|
6169
|
+
if (message2.success && message2.cid) {
|
|
6170
|
+
pending.resolve({ success: true, cid: message2.cid });
|
|
6171
|
+
} else {
|
|
6172
|
+
pending.resolve({ success: false, error: message2.error || "Storage failed" });
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
async store(options) {
|
|
6178
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
6179
|
+
await this.connect();
|
|
6180
|
+
}
|
|
6181
|
+
const requestId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
6182
|
+
const base64Data = btoa(String.fromCharCode(...options.data));
|
|
6183
|
+
const request = {
|
|
6184
|
+
type: "storage-request",
|
|
6185
|
+
requestId,
|
|
6186
|
+
data: base64Data,
|
|
6187
|
+
contentType: options.contentType,
|
|
6188
|
+
authorization: options.authorization
|
|
6189
|
+
};
|
|
6190
|
+
return new Promise((resolve, reject) => {
|
|
6191
|
+
const timeout = setTimeout(() => {
|
|
6192
|
+
this.pendingRequests.delete(requestId);
|
|
6193
|
+
reject(new Error("Storage request timeout"));
|
|
6194
|
+
}, options.timeout || 3e4);
|
|
6195
|
+
this.pendingRequests.set(requestId, { resolve, reject, timeout });
|
|
6196
|
+
try {
|
|
6197
|
+
this.ws.send(JSON.stringify(request));
|
|
6198
|
+
console.log("[Storage WS] Sent storage request:", requestId);
|
|
6199
|
+
} catch (error) {
|
|
6200
|
+
clearTimeout(timeout);
|
|
6201
|
+
this.pendingRequests.delete(requestId);
|
|
6202
|
+
reject(error);
|
|
6203
|
+
}
|
|
6204
|
+
});
|
|
6205
|
+
}
|
|
6206
|
+
disconnect() {
|
|
6207
|
+
if (this.ws) {
|
|
6208
|
+
this.ws.close();
|
|
6209
|
+
this.ws = null;
|
|
6210
|
+
}
|
|
6211
|
+
for (const [requestId, pending] of this.pendingRequests.entries()) {
|
|
6212
|
+
clearTimeout(pending.timeout);
|
|
6213
|
+
pending.reject(new Error("Client disconnected"));
|
|
6214
|
+
}
|
|
6215
|
+
this.pendingRequests.clear();
|
|
6216
|
+
}
|
|
6217
|
+
isConnected() {
|
|
6218
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
6219
|
+
}
|
|
6220
|
+
};
|
|
6221
|
+
|
|
6123
6222
|
// src/contracts/ContentRegistry.ts
|
|
6124
6223
|
var CONTENT_REGISTRY_ABI = [
|
|
6125
6224
|
"function registerContent(bytes32 cid, address owner, bytes32 appId) external",
|
|
@@ -6502,6 +6601,38 @@ Nonce: ${nonce}`;
|
|
|
6502
6601
|
console.warn("[ByteCave] Failed to create authorization:", err.message);
|
|
6503
6602
|
}
|
|
6504
6603
|
}
|
|
6604
|
+
if (this.config.relayWsUrl) {
|
|
6605
|
+
console.log("[ByteCave] Attempting WebSocket storage via relay");
|
|
6606
|
+
try {
|
|
6607
|
+
if (!this.storageWsClient) {
|
|
6608
|
+
this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
|
|
6609
|
+
}
|
|
6610
|
+
const wsAuth = authorization ? {
|
|
6611
|
+
signature: authorization.signature,
|
|
6612
|
+
address: authorization.sender,
|
|
6613
|
+
timestamp: authorization.timestamp,
|
|
6614
|
+
nonce: authorization.nonce
|
|
6615
|
+
} : void 0;
|
|
6616
|
+
const result = await this.storageWsClient.store({
|
|
6617
|
+
data: dataArray,
|
|
6618
|
+
contentType: mimeType || "application/octet-stream",
|
|
6619
|
+
authorization: wsAuth,
|
|
6620
|
+
timeout: 3e4
|
|
6621
|
+
});
|
|
6622
|
+
if (result.success && result.cid) {
|
|
6623
|
+
console.log("[ByteCave] \u2713 WebSocket storage successful:", result.cid);
|
|
6624
|
+
return {
|
|
6625
|
+
success: true,
|
|
6626
|
+
cid: result.cid,
|
|
6627
|
+
peerId: "relay-ws"
|
|
6628
|
+
};
|
|
6629
|
+
}
|
|
6630
|
+
console.warn("[ByteCave] WebSocket storage failed:", result.error);
|
|
6631
|
+
} catch (err) {
|
|
6632
|
+
console.warn("[ByteCave] WebSocket storage exception:", err.message);
|
|
6633
|
+
}
|
|
6634
|
+
}
|
|
6635
|
+
console.log("[ByteCave] Falling back to P2P storage");
|
|
6505
6636
|
const errors = [];
|
|
6506
6637
|
for (const peerId of storagePeerIds) {
|
|
6507
6638
|
console.log("[ByteCave] Attempting P2P store to peer:", peerId.slice(0, 12) + "...");
|
|
@@ -6531,8 +6662,8 @@ Nonce: ${nonce}`;
|
|
|
6531
6662
|
errors.push(errorMsg);
|
|
6532
6663
|
}
|
|
6533
6664
|
}
|
|
6534
|
-
console.error("[ByteCave] All storage
|
|
6535
|
-
return { success: false, error: `All storage
|
|
6665
|
+
console.error("[ByteCave] All storage methods failed. Errors:", errors);
|
|
6666
|
+
return { success: false, error: `All storage methods failed: ${errors.join("; ")}` };
|
|
6536
6667
|
}
|
|
6537
6668
|
/**
|
|
6538
6669
|
* Retrieve ciphertext from a node via P2P only (no HTTP fallback)
|
|
@@ -6700,16 +6831,9 @@ Nonce: ${nonce}`;
|
|
|
6700
6831
|
const peerInfo = this.knownPeers.get(peerId);
|
|
6701
6832
|
const relayAddrs = peerInfo?.relayAddrs;
|
|
6702
6833
|
if (relayAddrs && relayAddrs.length > 0 && this.node) {
|
|
6703
|
-
|
|
6704
|
-
const plainRelayAddrs = relayAddrs.filter((addr) => !addr.includes("/webrtc"));
|
|
6705
|
-
const addrToUse = webrtcRelayAddrs.length > 0 ? webrtcRelayAddrs[0] : plainRelayAddrs[0];
|
|
6706
|
-
console.log("[ByteCave] Dialing peer through relay:", peerId.slice(0, 12), {
|
|
6707
|
-
webrtcAvailable: webrtcRelayAddrs.length,
|
|
6708
|
-
plainAvailable: plainRelayAddrs.length,
|
|
6709
|
-
using: addrToUse
|
|
6710
|
-
});
|
|
6834
|
+
console.log("[ByteCave] Dialing peer through relay:", peerId.slice(0, 12), relayAddrs[0]);
|
|
6711
6835
|
try {
|
|
6712
|
-
const ma = (0, import_multiaddr.multiaddr)(
|
|
6836
|
+
const ma = (0, import_multiaddr.multiaddr)(relayAddrs[0]);
|
|
6713
6837
|
await this.node.dial(ma);
|
|
6714
6838
|
console.log("[ByteCave] Successfully dialed peer through relay");
|
|
6715
6839
|
} catch (dialError) {
|
|
@@ -6889,6 +7013,7 @@ function ByteCaveProvider({
|
|
|
6889
7013
|
appId,
|
|
6890
7014
|
relayPeers = [],
|
|
6891
7015
|
relayHttpUrl,
|
|
7016
|
+
relayWsUrl,
|
|
6892
7017
|
directNodeAddrs = []
|
|
6893
7018
|
}) {
|
|
6894
7019
|
const [connectionState, setConnectionState] = (0, import_react.useState)("disconnected");
|
|
@@ -6932,6 +7057,7 @@ function ByteCaveProvider({
|
|
|
6932
7057
|
directNodeAddrs,
|
|
6933
7058
|
relayPeers,
|
|
6934
7059
|
relayHttpUrl,
|
|
7060
|
+
relayWsUrl,
|
|
6935
7061
|
maxPeers: 10,
|
|
6936
7062
|
connectionTimeout: 3e4
|
|
6937
7063
|
});
|
package/dist/index.js
CHANGED
package/dist/provider.d.ts
CHANGED
|
@@ -53,8 +53,9 @@ interface ByteCaveProviderProps {
|
|
|
53
53
|
appId: string;
|
|
54
54
|
relayPeers?: string[];
|
|
55
55
|
relayHttpUrl?: string;
|
|
56
|
+
relayWsUrl?: string;
|
|
56
57
|
directNodeAddrs?: string[];
|
|
57
58
|
}
|
|
58
|
-
export declare function ByteCaveProvider({ children, vaultNodeRegistryAddress, contentRegistryAddress, rpcUrl, appId, relayPeers, relayHttpUrl, directNodeAddrs }: ByteCaveProviderProps): React.JSX.Element;
|
|
59
|
+
export declare function ByteCaveProvider({ children, vaultNodeRegistryAddress, contentRegistryAddress, rpcUrl, appId, relayPeers, relayHttpUrl, relayWsUrl, directNodeAddrs }: ByteCaveProviderProps): React.JSX.Element;
|
|
59
60
|
export declare function useByteCaveContext(): ByteCaveContextValue;
|
|
60
61
|
export {};
|
package/dist/react/index.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Storage Client for Browser
|
|
3
|
+
* Sends storage requests to relay, which routes to storage nodes
|
|
4
|
+
*/
|
|
5
|
+
export interface StoreViaWebSocketOptions {
|
|
6
|
+
data: Uint8Array;
|
|
7
|
+
contentType: string;
|
|
8
|
+
authorization?: {
|
|
9
|
+
signature: string;
|
|
10
|
+
address: string;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
nonce: string;
|
|
13
|
+
};
|
|
14
|
+
timeout?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class StorageWebSocketClient {
|
|
17
|
+
private relayUrl;
|
|
18
|
+
private ws;
|
|
19
|
+
private pendingRequests;
|
|
20
|
+
constructor(relayUrl: string);
|
|
21
|
+
connect(): Promise<void>;
|
|
22
|
+
private handleMessage;
|
|
23
|
+
store(options: StoreViaWebSocketOptions): Promise<{
|
|
24
|
+
success: boolean;
|
|
25
|
+
cid?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
}>;
|
|
28
|
+
disconnect(): void;
|
|
29
|
+
isConnected(): boolean;
|
|
30
|
+
}
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { fromString, toString } from 'uint8arrays';
|
|
|
20
20
|
import { ethers } from 'ethers';
|
|
21
21
|
import { ContractDiscovery, RelayDiscovery } from './discovery.js';
|
|
22
22
|
import { p2pProtocolClient } from './p2p-protocols.js';
|
|
23
|
+
import { StorageWebSocketClient } from './storage-websocket.js';
|
|
23
24
|
import { CONTENT_REGISTRY_ABI } from './contracts/ContentRegistry.js';
|
|
24
25
|
import type {
|
|
25
26
|
ByteCaveConfig,
|
|
@@ -37,6 +38,7 @@ export class ByteCaveClient {
|
|
|
37
38
|
private node: Libp2p | null = null;
|
|
38
39
|
private contractDiscovery?: ContractDiscovery; // Optional - only if contract address provided
|
|
39
40
|
private relayDiscovery?: RelayDiscovery; // Optional - for fast peer discovery via relay
|
|
41
|
+
private storageWsClient?: StorageWebSocketClient; // Optional - for WebSocket storage via relay
|
|
40
42
|
private config: ByteCaveConfig;
|
|
41
43
|
private knownPeers: Map<string, PeerInfo> = new Map();
|
|
42
44
|
private connectionState: ConnectionState = 'disconnected';
|
|
@@ -499,7 +501,46 @@ Nonce: ${nonce}`;
|
|
|
499
501
|
}
|
|
500
502
|
}
|
|
501
503
|
|
|
502
|
-
// Try
|
|
504
|
+
// Try WebSocket storage first if relay WS URL is configured
|
|
505
|
+
if (this.config.relayWsUrl) {
|
|
506
|
+
console.log('[ByteCave] Attempting WebSocket storage via relay');
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
if (!this.storageWsClient) {
|
|
510
|
+
this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const wsAuth = authorization ? {
|
|
514
|
+
signature: authorization.signature,
|
|
515
|
+
address: authorization.sender,
|
|
516
|
+
timestamp: authorization.timestamp,
|
|
517
|
+
nonce: authorization.nonce
|
|
518
|
+
} : undefined;
|
|
519
|
+
|
|
520
|
+
const result = await this.storageWsClient.store({
|
|
521
|
+
data: dataArray,
|
|
522
|
+
contentType: mimeType || 'application/octet-stream',
|
|
523
|
+
authorization: wsAuth,
|
|
524
|
+
timeout: 30000
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
if (result.success && result.cid) {
|
|
528
|
+
console.log('[ByteCave] ✓ WebSocket storage successful:', result.cid);
|
|
529
|
+
return {
|
|
530
|
+
success: true,
|
|
531
|
+
cid: result.cid,
|
|
532
|
+
peerId: 'relay-ws'
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
console.warn('[ByteCave] WebSocket storage failed:', result.error);
|
|
537
|
+
} catch (err: any) {
|
|
538
|
+
console.warn('[ByteCave] WebSocket storage exception:', err.message);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Fallback to P2P storage
|
|
543
|
+
console.log('[ByteCave] Falling back to P2P storage');
|
|
503
544
|
const errors: string[] = [];
|
|
504
545
|
for (const peerId of storagePeerIds) {
|
|
505
546
|
console.log('[ByteCave] Attempting P2P store to peer:', peerId.slice(0, 12) + '...');
|
|
@@ -532,8 +573,8 @@ Nonce: ${nonce}`;
|
|
|
532
573
|
}
|
|
533
574
|
}
|
|
534
575
|
|
|
535
|
-
console.error('[ByteCave] All storage
|
|
536
|
-
return { success: false, error: `All storage
|
|
576
|
+
console.error('[ByteCave] All storage methods failed. Errors:', errors);
|
|
577
|
+
return { success: false, error: `All storage methods failed: ${errors.join('; ')}` };
|
|
537
578
|
}
|
|
538
579
|
|
|
539
580
|
/**
|
|
@@ -750,20 +791,10 @@ Nonce: ${nonce}`;
|
|
|
750
791
|
const relayAddrs = (peerInfo as any)?.relayAddrs;
|
|
751
792
|
|
|
752
793
|
// If we have relay addresses, dial through relay first
|
|
753
|
-
// Prefer WebRTC circuit relay addresses for direct connections
|
|
754
794
|
if (relayAddrs && relayAddrs.length > 0 && this.node) {
|
|
755
|
-
|
|
756
|
-
const plainRelayAddrs = relayAddrs.filter((addr: string) => !addr.includes('/webrtc'));
|
|
757
|
-
const addrToUse = webrtcRelayAddrs.length > 0 ? webrtcRelayAddrs[0] : plainRelayAddrs[0];
|
|
758
|
-
|
|
759
|
-
console.log('[ByteCave] Dialing peer through relay:', peerId.slice(0, 12), {
|
|
760
|
-
webrtcAvailable: webrtcRelayAddrs.length,
|
|
761
|
-
plainAvailable: plainRelayAddrs.length,
|
|
762
|
-
using: addrToUse
|
|
763
|
-
});
|
|
764
|
-
|
|
795
|
+
console.log('[ByteCave] Dialing peer through relay:', peerId.slice(0, 12), relayAddrs[0]);
|
|
765
796
|
try {
|
|
766
|
-
const ma = multiaddr(
|
|
797
|
+
const ma = multiaddr(relayAddrs[0]);
|
|
767
798
|
await this.node.dial(ma as any);
|
|
768
799
|
console.log('[ByteCave] Successfully dialed peer through relay');
|
|
769
800
|
} catch (dialError) {
|
package/src/provider.tsx
CHANGED
|
@@ -56,6 +56,7 @@ interface ByteCaveProviderProps {
|
|
|
56
56
|
appId: string;
|
|
57
57
|
relayPeers?: string[];
|
|
58
58
|
relayHttpUrl?: string;
|
|
59
|
+
relayWsUrl?: string;
|
|
59
60
|
directNodeAddrs?: string[];
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -69,6 +70,7 @@ export function ByteCaveProvider({
|
|
|
69
70
|
appId,
|
|
70
71
|
relayPeers = [],
|
|
71
72
|
relayHttpUrl,
|
|
73
|
+
relayWsUrl,
|
|
72
74
|
directNodeAddrs = []
|
|
73
75
|
}: ByteCaveProviderProps) {
|
|
74
76
|
const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');
|
|
@@ -120,6 +122,7 @@ export function ByteCaveProvider({
|
|
|
120
122
|
directNodeAddrs,
|
|
121
123
|
relayPeers,
|
|
122
124
|
relayHttpUrl,
|
|
125
|
+
relayWsUrl,
|
|
123
126
|
maxPeers: 10,
|
|
124
127
|
connectionTimeout: 30000
|
|
125
128
|
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Storage Client for Browser
|
|
3
|
+
* Sends storage requests to relay, which routes to storage nodes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
interface StorageRequestMessage {
|
|
7
|
+
type: 'storage-request';
|
|
8
|
+
requestId: string;
|
|
9
|
+
data: string; // base64
|
|
10
|
+
contentType: string;
|
|
11
|
+
authorization?: {
|
|
12
|
+
signature: string;
|
|
13
|
+
address: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
nonce: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface StorageResponseMessage {
|
|
20
|
+
type: 'storage-response';
|
|
21
|
+
requestId: string;
|
|
22
|
+
success: boolean;
|
|
23
|
+
cid?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type Message = StorageRequestMessage | StorageResponseMessage;
|
|
28
|
+
|
|
29
|
+
export interface StoreViaWebSocketOptions {
|
|
30
|
+
data: Uint8Array;
|
|
31
|
+
contentType: string;
|
|
32
|
+
authorization?: {
|
|
33
|
+
signature: string;
|
|
34
|
+
address: string;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
nonce: string;
|
|
37
|
+
};
|
|
38
|
+
timeout?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class StorageWebSocketClient {
|
|
42
|
+
private ws: WebSocket | null = null;
|
|
43
|
+
private pendingRequests: Map<string, {
|
|
44
|
+
resolve: (result: { success: boolean; cid?: string; error?: string }) => void;
|
|
45
|
+
reject: (error: Error) => void;
|
|
46
|
+
timeout: NodeJS.Timeout;
|
|
47
|
+
}> = new Map();
|
|
48
|
+
|
|
49
|
+
constructor(private relayUrl: string) {}
|
|
50
|
+
|
|
51
|
+
async connect(): Promise<void> {
|
|
52
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
this.ws = new WebSocket(this.relayUrl);
|
|
58
|
+
|
|
59
|
+
this.ws.onopen = () => {
|
|
60
|
+
console.log('[Storage WS] Connected to relay');
|
|
61
|
+
resolve();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
this.ws.onmessage = (event) => {
|
|
65
|
+
try {
|
|
66
|
+
const message = JSON.parse(event.data) as Message;
|
|
67
|
+
this.handleMessage(message);
|
|
68
|
+
} catch (error: any) {
|
|
69
|
+
console.error('[Storage WS] Failed to parse message:', error.message);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
this.ws.onerror = (error) => {
|
|
74
|
+
console.error('[Storage WS] WebSocket error:', error);
|
|
75
|
+
reject(new Error('WebSocket connection failed'));
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this.ws.onclose = () => {
|
|
79
|
+
console.log('[Storage WS] Connection closed');
|
|
80
|
+
this.ws = null;
|
|
81
|
+
// Reject all pending requests
|
|
82
|
+
for (const [requestId, pending] of this.pendingRequests.entries()) {
|
|
83
|
+
clearTimeout(pending.timeout);
|
|
84
|
+
pending.reject(new Error('WebSocket connection closed'));
|
|
85
|
+
}
|
|
86
|
+
this.pendingRequests.clear();
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private handleMessage(message: Message): void {
|
|
92
|
+
if (message.type === 'storage-response') {
|
|
93
|
+
const pending = this.pendingRequests.get(message.requestId);
|
|
94
|
+
if (pending) {
|
|
95
|
+
clearTimeout(pending.timeout);
|
|
96
|
+
this.pendingRequests.delete(message.requestId);
|
|
97
|
+
|
|
98
|
+
if (message.success && message.cid) {
|
|
99
|
+
pending.resolve({ success: true, cid: message.cid });
|
|
100
|
+
} else {
|
|
101
|
+
pending.resolve({ success: false, error: message.error || 'Storage failed' });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async store(options: StoreViaWebSocketOptions): Promise<{ success: boolean; cid?: string; error?: string }> {
|
|
108
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
109
|
+
await this.connect();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const requestId = Math.random().toString(36).substring(2, 15) +
|
|
113
|
+
Math.random().toString(36).substring(2, 15);
|
|
114
|
+
|
|
115
|
+
// Convert Uint8Array to base64
|
|
116
|
+
const base64Data = btoa(String.fromCharCode(...options.data));
|
|
117
|
+
|
|
118
|
+
const request: StorageRequestMessage = {
|
|
119
|
+
type: 'storage-request',
|
|
120
|
+
requestId,
|
|
121
|
+
data: base64Data,
|
|
122
|
+
contentType: options.contentType,
|
|
123
|
+
authorization: options.authorization
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const timeout = setTimeout(() => {
|
|
128
|
+
this.pendingRequests.delete(requestId);
|
|
129
|
+
reject(new Error('Storage request timeout'));
|
|
130
|
+
}, options.timeout || 30000);
|
|
131
|
+
|
|
132
|
+
this.pendingRequests.set(requestId, { resolve, reject, timeout });
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
this.ws!.send(JSON.stringify(request));
|
|
136
|
+
console.log('[Storage WS] Sent storage request:', requestId);
|
|
137
|
+
} catch (error: any) {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
this.pendingRequests.delete(requestId);
|
|
140
|
+
reject(error);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
disconnect(): void {
|
|
146
|
+
if (this.ws) {
|
|
147
|
+
this.ws.close();
|
|
148
|
+
this.ws = null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Clear all pending requests
|
|
152
|
+
for (const [requestId, pending] of this.pendingRequests.entries()) {
|
|
153
|
+
clearTimeout(pending.timeout);
|
|
154
|
+
pending.reject(new Error('Client disconnected'));
|
|
155
|
+
}
|
|
156
|
+
this.pendingRequests.clear();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
isConnected(): boolean {
|
|
160
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
161
|
+
}
|
|
162
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface ByteCaveConfig {
|
|
|
10
10
|
directNodeAddrs?: string[]; // Direct node multiaddrs for WebRTC connections (no relay)
|
|
11
11
|
relayPeers?: string[]; // Relay node multiaddrs for circuit relay (e.g., /ip4/127.0.0.1/tcp/4002/ws/p2p/...)
|
|
12
12
|
relayHttpUrl?: string; // Optional - Relay HTTP URL for instant peer discovery (e.g., http://localhost:9090)
|
|
13
|
+
relayWsUrl?: string; // Optional - Relay WebSocket URL for browser storage (e.g., ws://localhost:4003)
|
|
13
14
|
maxPeers?: number;
|
|
14
15
|
connectionTimeout?: number;
|
|
15
16
|
}
|