@gethashd/bytecave-browser 1.0.30 → 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.
@@ -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 peers failed. Errors:", errors);
6482
- return { success: false, error: `All storage peers failed: ${errors.join("; ")}` };
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)
@@ -6829,6 +6960,7 @@ function ByteCaveProvider({
6829
6960
  appId,
6830
6961
  relayPeers = [],
6831
6962
  relayHttpUrl,
6963
+ relayWsUrl,
6832
6964
  directNodeAddrs = []
6833
6965
  }) {
6834
6966
  const [connectionState, setConnectionState] = useState("disconnected");
@@ -6872,6 +7004,7 @@ function ByteCaveProvider({
6872
7004
  directNodeAddrs,
6873
7005
  relayPeers,
6874
7006
  relayHttpUrl,
7007
+ relayWsUrl,
6875
7008
  maxPeers: 10,
6876
7009
  connectionTimeout: 3e4
6877
7010
  });
package/dist/client.d.ts CHANGED
@@ -9,6 +9,7 @@ export declare class ByteCaveClient {
9
9
  private node;
10
10
  private contractDiscovery?;
11
11
  private relayDiscovery?;
12
+ private storageWsClient?;
12
13
  private config;
13
14
  private knownPeers;
14
15
  private connectionState;
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 peers failed. Errors:", errors);
6535
- return { success: false, error: `All storage peers failed: ${errors.join("; ")}` };
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)
@@ -6882,6 +7013,7 @@ function ByteCaveProvider({
6882
7013
  appId,
6883
7014
  relayPeers = [],
6884
7015
  relayHttpUrl,
7016
+ relayWsUrl,
6885
7017
  directNodeAddrs = []
6886
7018
  }) {
6887
7019
  const [connectionState, setConnectionState] = (0, import_react.useState)("disconnected");
@@ -6925,6 +7057,7 @@ function ByteCaveProvider({
6925
7057
  directNodeAddrs,
6926
7058
  relayPeers,
6927
7059
  relayHttpUrl,
7060
+ relayWsUrl,
6928
7061
  maxPeers: 10,
6929
7062
  connectionTimeout: 3e4
6930
7063
  });
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  useHashdImage,
14
14
  useHashdMedia,
15
15
  useHashdUrl
16
- } from "./chunk-JJDGONLY.js";
16
+ } from "./chunk-LPLEL6EN.js";
17
17
  import {
18
18
  clearHashdCache,
19
19
  createHashdUrl,
@@ -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 {};
@@ -8,7 +8,7 @@ import {
8
8
  useHashdImage,
9
9
  useHashdMedia,
10
10
  useHashdUrl
11
- } from "../chunk-JJDGONLY.js";
11
+ } from "../chunk-LPLEL6EN.js";
12
12
  import "../chunk-EEZWRIUI.js";
13
13
  export {
14
14
  HashdAudio,
@@ -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
@@ -9,6 +9,7 @@ export interface ByteCaveConfig {
9
9
  directNodeAddrs?: string[];
10
10
  relayPeers?: string[];
11
11
  relayHttpUrl?: string;
12
+ relayWsUrl?: string;
12
13
  maxPeers?: number;
13
14
  connectionTimeout?: number;
14
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethashd/bytecave-browser",
3
- "version": "1.0.30",
3
+ "version": "1.0.31",
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
@@ -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 each storage peer until one succeeds
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 peers failed. Errors:', errors);
536
- return { success: false, error: `All storage peers failed: ${errors.join('; ')}` };
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
  /**
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
  }