@gethashd/bytecave-browser 1.0.30 → 1.0.32
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-JJDGONLY.js → chunk-KFAG3W6R.js} +146 -21
- package/dist/client.d.ts +1 -0
- package/dist/index.cjs +146 -21
- 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 +68 -32
- 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",
|
|
@@ -6394,25 +6493,6 @@ var ByteCaveClient = class {
|
|
|
6394
6493
|
* @param signer - Ethers signer for authorization (optional, but required for most nodes)
|
|
6395
6494
|
*/
|
|
6396
6495
|
async store(data, mimeType, signer) {
|
|
6397
|
-
if (!this.node) {
|
|
6398
|
-
return { success: false, error: "P2P node not initialized" };
|
|
6399
|
-
}
|
|
6400
|
-
const allPeers = this.node.getPeers();
|
|
6401
|
-
const connectedPeerIds = allPeers.map((p) => p.toString());
|
|
6402
|
-
console.log("[ByteCave] Store - connected storage peers:", connectedPeerIds.length);
|
|
6403
|
-
console.log("[ByteCave] Store - knownPeers with registration info:", this.knownPeers.size);
|
|
6404
|
-
if (connectedPeerIds.length === 0) {
|
|
6405
|
-
return { success: false, error: "No storage peers available" };
|
|
6406
|
-
}
|
|
6407
|
-
const registeredPeerIds = Array.from(this.knownPeers.values()).filter((p) => p.isRegistered && connectedPeerIds.includes(p.peerId)).map((p) => p.peerId);
|
|
6408
|
-
const storagePeerIds = registeredPeerIds.length > 0 ? [...registeredPeerIds, ...connectedPeerIds.filter((id) => !registeredPeerIds.includes(id))] : connectedPeerIds;
|
|
6409
|
-
console.log(
|
|
6410
|
-
"[ByteCave] Store - peer order (registered first):",
|
|
6411
|
-
storagePeerIds.map((id) => id.slice(0, 12)).join(", "),
|
|
6412
|
-
"(registered:",
|
|
6413
|
-
registeredPeerIds.length,
|
|
6414
|
-
")"
|
|
6415
|
-
);
|
|
6416
6496
|
const dataArray = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
6417
6497
|
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
6418
6498
|
if (dataArray.length > MAX_FILE_SIZE) {
|
|
@@ -6449,6 +6529,49 @@ Nonce: ${nonce}`;
|
|
|
6449
6529
|
console.warn("[ByteCave] Failed to create authorization:", err.message);
|
|
6450
6530
|
}
|
|
6451
6531
|
}
|
|
6532
|
+
if (this.config.relayWsUrl) {
|
|
6533
|
+
console.log("[ByteCave] Attempting WebSocket storage via relay");
|
|
6534
|
+
try {
|
|
6535
|
+
if (!this.storageWsClient) {
|
|
6536
|
+
this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
|
|
6537
|
+
}
|
|
6538
|
+
const wsAuth = authorization ? {
|
|
6539
|
+
signature: authorization.signature,
|
|
6540
|
+
address: authorization.sender,
|
|
6541
|
+
timestamp: authorization.timestamp,
|
|
6542
|
+
nonce: authorization.nonce
|
|
6543
|
+
} : void 0;
|
|
6544
|
+
const result = await this.storageWsClient.store({
|
|
6545
|
+
data: dataArray,
|
|
6546
|
+
contentType: mimeType || "application/octet-stream",
|
|
6547
|
+
authorization: wsAuth,
|
|
6548
|
+
timeout: 3e4
|
|
6549
|
+
});
|
|
6550
|
+
if (result.success && result.cid) {
|
|
6551
|
+
console.log("[ByteCave] \u2713 WebSocket storage successful:", result.cid);
|
|
6552
|
+
return {
|
|
6553
|
+
success: true,
|
|
6554
|
+
cid: result.cid,
|
|
6555
|
+
peerId: "relay-ws"
|
|
6556
|
+
};
|
|
6557
|
+
}
|
|
6558
|
+
console.warn("[ByteCave] WebSocket storage failed:", result.error);
|
|
6559
|
+
} catch (err) {
|
|
6560
|
+
console.warn("[ByteCave] WebSocket storage exception:", err.message);
|
|
6561
|
+
}
|
|
6562
|
+
}
|
|
6563
|
+
if (!this.node) {
|
|
6564
|
+
return { success: false, error: "WebSocket storage failed and P2P node not initialized" };
|
|
6565
|
+
}
|
|
6566
|
+
console.log("[ByteCave] Falling back to P2P storage");
|
|
6567
|
+
const allPeers = this.node.getPeers();
|
|
6568
|
+
const connectedPeerIds = allPeers.map((p) => p.toString());
|
|
6569
|
+
console.log("[ByteCave] Store - connected storage peers:", connectedPeerIds.length);
|
|
6570
|
+
if (connectedPeerIds.length === 0) {
|
|
6571
|
+
return { success: false, error: "WebSocket storage failed and no P2P peers available" };
|
|
6572
|
+
}
|
|
6573
|
+
const registeredPeerIds = Array.from(this.knownPeers.values()).filter((p) => p.isRegistered && connectedPeerIds.includes(p.peerId)).map((p) => p.peerId);
|
|
6574
|
+
const storagePeerIds = registeredPeerIds.length > 0 ? [...registeredPeerIds, ...connectedPeerIds.filter((id) => !registeredPeerIds.includes(id))] : connectedPeerIds;
|
|
6452
6575
|
const errors = [];
|
|
6453
6576
|
for (const peerId of storagePeerIds) {
|
|
6454
6577
|
console.log("[ByteCave] Attempting P2P store to peer:", peerId.slice(0, 12) + "...");
|
|
@@ -6478,8 +6601,8 @@ Nonce: ${nonce}`;
|
|
|
6478
6601
|
errors.push(errorMsg);
|
|
6479
6602
|
}
|
|
6480
6603
|
}
|
|
6481
|
-
console.error("[ByteCave] All storage
|
|
6482
|
-
return { success: false, error: `All storage
|
|
6604
|
+
console.error("[ByteCave] All storage methods failed. Errors:", errors);
|
|
6605
|
+
return { success: false, error: `All storage methods failed: ${errors.join("; ")}` };
|
|
6483
6606
|
}
|
|
6484
6607
|
/**
|
|
6485
6608
|
* Retrieve ciphertext from a node via P2P only (no HTTP fallback)
|
|
@@ -6829,6 +6952,7 @@ function ByteCaveProvider({
|
|
|
6829
6952
|
appId,
|
|
6830
6953
|
relayPeers = [],
|
|
6831
6954
|
relayHttpUrl,
|
|
6955
|
+
relayWsUrl,
|
|
6832
6956
|
directNodeAddrs = []
|
|
6833
6957
|
}) {
|
|
6834
6958
|
const [connectionState, setConnectionState] = useState("disconnected");
|
|
@@ -6872,6 +6996,7 @@ function ByteCaveProvider({
|
|
|
6872
6996
|
directNodeAddrs,
|
|
6873
6997
|
relayPeers,
|
|
6874
6998
|
relayHttpUrl,
|
|
6999
|
+
relayWsUrl,
|
|
6875
7000
|
maxPeers: 10,
|
|
6876
7001
|
connectionTimeout: 3e4
|
|
6877
7002
|
});
|
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",
|
|
@@ -6447,25 +6546,6 @@ var ByteCaveClient = class {
|
|
|
6447
6546
|
* @param signer - Ethers signer for authorization (optional, but required for most nodes)
|
|
6448
6547
|
*/
|
|
6449
6548
|
async store(data, mimeType, signer) {
|
|
6450
|
-
if (!this.node) {
|
|
6451
|
-
return { success: false, error: "P2P node not initialized" };
|
|
6452
|
-
}
|
|
6453
|
-
const allPeers = this.node.getPeers();
|
|
6454
|
-
const connectedPeerIds = allPeers.map((p) => p.toString());
|
|
6455
|
-
console.log("[ByteCave] Store - connected storage peers:", connectedPeerIds.length);
|
|
6456
|
-
console.log("[ByteCave] Store - knownPeers with registration info:", this.knownPeers.size);
|
|
6457
|
-
if (connectedPeerIds.length === 0) {
|
|
6458
|
-
return { success: false, error: "No storage peers available" };
|
|
6459
|
-
}
|
|
6460
|
-
const registeredPeerIds = Array.from(this.knownPeers.values()).filter((p) => p.isRegistered && connectedPeerIds.includes(p.peerId)).map((p) => p.peerId);
|
|
6461
|
-
const storagePeerIds = registeredPeerIds.length > 0 ? [...registeredPeerIds, ...connectedPeerIds.filter((id) => !registeredPeerIds.includes(id))] : connectedPeerIds;
|
|
6462
|
-
console.log(
|
|
6463
|
-
"[ByteCave] Store - peer order (registered first):",
|
|
6464
|
-
storagePeerIds.map((id) => id.slice(0, 12)).join(", "),
|
|
6465
|
-
"(registered:",
|
|
6466
|
-
registeredPeerIds.length,
|
|
6467
|
-
")"
|
|
6468
|
-
);
|
|
6469
6549
|
const dataArray = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
6470
6550
|
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
6471
6551
|
if (dataArray.length > MAX_FILE_SIZE) {
|
|
@@ -6502,6 +6582,49 @@ Nonce: ${nonce}`;
|
|
|
6502
6582
|
console.warn("[ByteCave] Failed to create authorization:", err.message);
|
|
6503
6583
|
}
|
|
6504
6584
|
}
|
|
6585
|
+
if (this.config.relayWsUrl) {
|
|
6586
|
+
console.log("[ByteCave] Attempting WebSocket storage via relay");
|
|
6587
|
+
try {
|
|
6588
|
+
if (!this.storageWsClient) {
|
|
6589
|
+
this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
|
|
6590
|
+
}
|
|
6591
|
+
const wsAuth = authorization ? {
|
|
6592
|
+
signature: authorization.signature,
|
|
6593
|
+
address: authorization.sender,
|
|
6594
|
+
timestamp: authorization.timestamp,
|
|
6595
|
+
nonce: authorization.nonce
|
|
6596
|
+
} : void 0;
|
|
6597
|
+
const result = await this.storageWsClient.store({
|
|
6598
|
+
data: dataArray,
|
|
6599
|
+
contentType: mimeType || "application/octet-stream",
|
|
6600
|
+
authorization: wsAuth,
|
|
6601
|
+
timeout: 3e4
|
|
6602
|
+
});
|
|
6603
|
+
if (result.success && result.cid) {
|
|
6604
|
+
console.log("[ByteCave] \u2713 WebSocket storage successful:", result.cid);
|
|
6605
|
+
return {
|
|
6606
|
+
success: true,
|
|
6607
|
+
cid: result.cid,
|
|
6608
|
+
peerId: "relay-ws"
|
|
6609
|
+
};
|
|
6610
|
+
}
|
|
6611
|
+
console.warn("[ByteCave] WebSocket storage failed:", result.error);
|
|
6612
|
+
} catch (err) {
|
|
6613
|
+
console.warn("[ByteCave] WebSocket storage exception:", err.message);
|
|
6614
|
+
}
|
|
6615
|
+
}
|
|
6616
|
+
if (!this.node) {
|
|
6617
|
+
return { success: false, error: "WebSocket storage failed and P2P node not initialized" };
|
|
6618
|
+
}
|
|
6619
|
+
console.log("[ByteCave] Falling back to P2P storage");
|
|
6620
|
+
const allPeers = this.node.getPeers();
|
|
6621
|
+
const connectedPeerIds = allPeers.map((p) => p.toString());
|
|
6622
|
+
console.log("[ByteCave] Store - connected storage peers:", connectedPeerIds.length);
|
|
6623
|
+
if (connectedPeerIds.length === 0) {
|
|
6624
|
+
return { success: false, error: "WebSocket storage failed and no P2P peers available" };
|
|
6625
|
+
}
|
|
6626
|
+
const registeredPeerIds = Array.from(this.knownPeers.values()).filter((p) => p.isRegistered && connectedPeerIds.includes(p.peerId)).map((p) => p.peerId);
|
|
6627
|
+
const storagePeerIds = registeredPeerIds.length > 0 ? [...registeredPeerIds, ...connectedPeerIds.filter((id) => !registeredPeerIds.includes(id))] : connectedPeerIds;
|
|
6505
6628
|
const errors = [];
|
|
6506
6629
|
for (const peerId of storagePeerIds) {
|
|
6507
6630
|
console.log("[ByteCave] Attempting P2P store to peer:", peerId.slice(0, 12) + "...");
|
|
@@ -6531,8 +6654,8 @@ Nonce: ${nonce}`;
|
|
|
6531
6654
|
errors.push(errorMsg);
|
|
6532
6655
|
}
|
|
6533
6656
|
}
|
|
6534
|
-
console.error("[ByteCave] All storage
|
|
6535
|
-
return { success: false, error: `All storage
|
|
6657
|
+
console.error("[ByteCave] All storage methods failed. Errors:", errors);
|
|
6658
|
+
return { success: false, error: `All storage methods failed: ${errors.join("; ")}` };
|
|
6536
6659
|
}
|
|
6537
6660
|
/**
|
|
6538
6661
|
* Retrieve ciphertext from a node via P2P only (no HTTP fallback)
|
|
@@ -6882,6 +7005,7 @@ function ByteCaveProvider({
|
|
|
6882
7005
|
appId,
|
|
6883
7006
|
relayPeers = [],
|
|
6884
7007
|
relayHttpUrl,
|
|
7008
|
+
relayWsUrl,
|
|
6885
7009
|
directNodeAddrs = []
|
|
6886
7010
|
}) {
|
|
6887
7011
|
const [connectionState, setConnectionState] = (0, import_react.useState)("disconnected");
|
|
@@ -6925,6 +7049,7 @@ function ByteCaveProvider({
|
|
|
6925
7049
|
directNodeAddrs,
|
|
6926
7050
|
relayPeers,
|
|
6927
7051
|
relayHttpUrl,
|
|
7052
|
+
relayWsUrl,
|
|
6928
7053
|
maxPeers: 10,
|
|
6929
7054
|
connectionTimeout: 3e4
|
|
6930
7055
|
});
|
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';
|
|
@@ -423,35 +425,6 @@ export class ByteCaveClient {
|
|
|
423
425
|
* @param signer - Ethers signer for authorization (optional, but required for most nodes)
|
|
424
426
|
*/
|
|
425
427
|
async store(data: Uint8Array | ArrayBuffer, mimeType?: string, signer?: any): Promise<StoreResult> {
|
|
426
|
-
if (!this.node) {
|
|
427
|
-
return { success: false, error: 'P2P node not initialized' };
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Get all connected peers
|
|
431
|
-
const allPeers = this.node.getPeers();
|
|
432
|
-
const connectedPeerIds = allPeers.map(p => p.toString());
|
|
433
|
-
|
|
434
|
-
console.log('[ByteCave] Store - connected storage peers:', connectedPeerIds.length);
|
|
435
|
-
console.log('[ByteCave] Store - knownPeers with registration info:', this.knownPeers.size);
|
|
436
|
-
|
|
437
|
-
if (connectedPeerIds.length === 0) {
|
|
438
|
-
return { success: false, error: 'No storage peers available' };
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Prioritize registered peers from knownPeers (populated via floodsub announcements)
|
|
442
|
-
// If no registered peers known yet, use all connected peers
|
|
443
|
-
const registeredPeerIds = Array.from(this.knownPeers.values())
|
|
444
|
-
.filter(p => p.isRegistered && connectedPeerIds.includes(p.peerId))
|
|
445
|
-
.map(p => p.peerId);
|
|
446
|
-
|
|
447
|
-
const storagePeerIds = registeredPeerIds.length > 0
|
|
448
|
-
? [...registeredPeerIds, ...connectedPeerIds.filter(id => !registeredPeerIds.includes(id))]
|
|
449
|
-
: connectedPeerIds;
|
|
450
|
-
|
|
451
|
-
console.log('[ByteCave] Store - peer order (registered first):',
|
|
452
|
-
storagePeerIds.map(id => id.slice(0, 12)).join(', '),
|
|
453
|
-
'(registered:', registeredPeerIds.length, ')');
|
|
454
|
-
|
|
455
428
|
const dataArray = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
456
429
|
|
|
457
430
|
// Validate file size (5MB limit)
|
|
@@ -499,7 +472,70 @@ Nonce: ${nonce}`;
|
|
|
499
472
|
}
|
|
500
473
|
}
|
|
501
474
|
|
|
502
|
-
// Try
|
|
475
|
+
// Try WebSocket storage first if relay WS URL is configured
|
|
476
|
+
if (this.config.relayWsUrl) {
|
|
477
|
+
console.log('[ByteCave] Attempting WebSocket storage via relay');
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
if (!this.storageWsClient) {
|
|
481
|
+
this.storageWsClient = new StorageWebSocketClient(this.config.relayWsUrl);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const wsAuth = authorization ? {
|
|
485
|
+
signature: authorization.signature,
|
|
486
|
+
address: authorization.sender,
|
|
487
|
+
timestamp: authorization.timestamp,
|
|
488
|
+
nonce: authorization.nonce
|
|
489
|
+
} : undefined;
|
|
490
|
+
|
|
491
|
+
const result = await this.storageWsClient.store({
|
|
492
|
+
data: dataArray,
|
|
493
|
+
contentType: mimeType || 'application/octet-stream',
|
|
494
|
+
authorization: wsAuth,
|
|
495
|
+
timeout: 30000
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
if (result.success && result.cid) {
|
|
499
|
+
console.log('[ByteCave] ✓ WebSocket storage successful:', result.cid);
|
|
500
|
+
return {
|
|
501
|
+
success: true,
|
|
502
|
+
cid: result.cid,
|
|
503
|
+
peerId: 'relay-ws'
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
console.warn('[ByteCave] WebSocket storage failed:', result.error);
|
|
508
|
+
} catch (err: any) {
|
|
509
|
+
console.warn('[ByteCave] WebSocket storage exception:', err.message);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Fallback to P2P storage
|
|
514
|
+
if (!this.node) {
|
|
515
|
+
return { success: false, error: 'WebSocket storage failed and P2P node not initialized' };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
console.log('[ByteCave] Falling back to P2P storage');
|
|
519
|
+
|
|
520
|
+
// Get all connected peers
|
|
521
|
+
const allPeers = this.node.getPeers();
|
|
522
|
+
const connectedPeerIds = allPeers.map(p => p.toString());
|
|
523
|
+
|
|
524
|
+
console.log('[ByteCave] Store - connected storage peers:', connectedPeerIds.length);
|
|
525
|
+
|
|
526
|
+
if (connectedPeerIds.length === 0) {
|
|
527
|
+
return { success: false, error: 'WebSocket storage failed and no P2P peers available' };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Prioritize registered peers from knownPeers
|
|
531
|
+
const registeredPeerIds = Array.from(this.knownPeers.values())
|
|
532
|
+
.filter(p => p.isRegistered && connectedPeerIds.includes(p.peerId))
|
|
533
|
+
.map(p => p.peerId);
|
|
534
|
+
|
|
535
|
+
const storagePeerIds = registeredPeerIds.length > 0
|
|
536
|
+
? [...registeredPeerIds, ...connectedPeerIds.filter(id => !registeredPeerIds.includes(id))]
|
|
537
|
+
: connectedPeerIds;
|
|
538
|
+
|
|
503
539
|
const errors: string[] = [];
|
|
504
540
|
for (const peerId of storagePeerIds) {
|
|
505
541
|
console.log('[ByteCave] Attempting P2P store to peer:', peerId.slice(0, 12) + '...');
|
|
@@ -532,8 +568,8 @@ Nonce: ${nonce}`;
|
|
|
532
568
|
}
|
|
533
569
|
}
|
|
534
570
|
|
|
535
|
-
console.error('[ByteCave] All storage
|
|
536
|
-
return { success: false, error: `All storage
|
|
571
|
+
console.error('[ByteCave] All storage methods failed. Errors:', errors);
|
|
572
|
+
return { success: false, error: `All storage methods failed: ${errors.join('; ')}` };
|
|
537
573
|
}
|
|
538
574
|
|
|
539
575
|
/**
|
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
|
}
|