@hashtree/worker 0.2.0 → 0.2.1
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/package.json +7 -3
- package/src/app-runtime.ts +393 -0
- package/src/capabilities/blossomBandwidthTracker.ts +74 -0
- package/src/capabilities/blossomTransport.ts +179 -0
- package/src/capabilities/connectivity.ts +54 -0
- package/src/capabilities/idbStorage.ts +94 -0
- package/src/capabilities/meshRouterStore.ts +426 -0
- package/src/capabilities/rootResolver.ts +497 -0
- package/src/client-id.ts +137 -0
- package/src/client.ts +501 -0
- package/{dist/entry.js → src/entry.ts} +1 -1
- package/src/htree-path.ts +53 -0
- package/src/htree-url.ts +156 -0
- package/src/index.ts +76 -0
- package/src/mediaStreaming.ts +64 -0
- package/src/p2p/boundedQueue.ts +168 -0
- package/src/p2p/errorMessage.ts +6 -0
- package/src/p2p/index.ts +48 -0
- package/src/p2p/lruCache.ts +78 -0
- package/src/p2p/meshQueryRouter.ts +361 -0
- package/src/p2p/protocol.ts +11 -0
- package/src/p2p/queryForwardingMachine.ts +197 -0
- package/src/p2p/signaling.ts +284 -0
- package/src/p2p/uploadRateLimiter.ts +85 -0
- package/src/p2p/webrtcController.ts +1168 -0
- package/src/p2p/webrtcProxy.ts +519 -0
- package/src/privacyGuards.ts +31 -0
- package/src/protocol.ts +124 -0
- package/src/relay/identity.ts +86 -0
- package/src/relay/mediaHandler.ts +1633 -0
- package/src/relay/ndk.ts +590 -0
- package/{dist/relay/nostr-wasm.js → src/relay/nostr-wasm.ts} +4 -1
- package/src/relay/nostr.ts +249 -0
- package/src/relay/protocol.ts +361 -0
- package/src/relay/publicAssetUrl.ts +25 -0
- package/src/relay/rootPathResolver.ts +50 -0
- package/src/relay/shims.d.ts +17 -0
- package/src/relay/signing.ts +332 -0
- package/src/relay/treeRootCache.ts +354 -0
- package/src/relay/treeRootSubscription.ts +577 -0
- package/src/relay/utils/constants.ts +139 -0
- package/src/relay/utils/errorMessage.ts +7 -0
- package/src/relay/utils/lruCache.ts +79 -0
- package/src/relay/webrtc.ts +5 -0
- package/src/relay/webrtcSignaling.ts +108 -0
- package/src/relay/worker.ts +1787 -0
- package/src/relay-client.ts +265 -0
- package/src/relay-entry.ts +1 -0
- package/src/runtime-network.ts +134 -0
- package/src/runtime.ts +153 -0
- package/{dist/transferableBytes.js → src/transferableBytes.ts} +2 -3
- package/src/tree-root.ts +851 -0
- package/src/types.ts +8 -0
- package/src/worker.ts +975 -0
- package/dist/app-runtime.d.ts +0 -60
- package/dist/app-runtime.d.ts.map +0 -1
- package/dist/app-runtime.js +0 -271
- package/dist/app-runtime.js.map +0 -1
- package/dist/capabilities/blossomBandwidthTracker.d.ts +0 -26
- package/dist/capabilities/blossomBandwidthTracker.d.ts.map +0 -1
- package/dist/capabilities/blossomBandwidthTracker.js +0 -53
- package/dist/capabilities/blossomBandwidthTracker.js.map +0 -1
- package/dist/capabilities/blossomTransport.d.ts +0 -22
- package/dist/capabilities/blossomTransport.d.ts.map +0 -1
- package/dist/capabilities/blossomTransport.js +0 -139
- package/dist/capabilities/blossomTransport.js.map +0 -1
- package/dist/capabilities/connectivity.d.ts +0 -3
- package/dist/capabilities/connectivity.d.ts.map +0 -1
- package/dist/capabilities/connectivity.js +0 -49
- package/dist/capabilities/connectivity.js.map +0 -1
- package/dist/capabilities/idbStorage.d.ts +0 -25
- package/dist/capabilities/idbStorage.d.ts.map +0 -1
- package/dist/capabilities/idbStorage.js +0 -73
- package/dist/capabilities/idbStorage.js.map +0 -1
- package/dist/capabilities/meshRouterStore.d.ts +0 -71
- package/dist/capabilities/meshRouterStore.d.ts.map +0 -1
- package/dist/capabilities/meshRouterStore.js +0 -316
- package/dist/capabilities/meshRouterStore.js.map +0 -1
- package/dist/capabilities/rootResolver.d.ts +0 -10
- package/dist/capabilities/rootResolver.d.ts.map +0 -1
- package/dist/capabilities/rootResolver.js +0 -392
- package/dist/capabilities/rootResolver.js.map +0 -1
- package/dist/client-id.d.ts +0 -18
- package/dist/client-id.d.ts.map +0 -1
- package/dist/client-id.js +0 -98
- package/dist/client-id.js.map +0 -1
- package/dist/client.d.ts +0 -61
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -417
- package/dist/client.js.map +0 -1
- package/dist/entry.d.ts +0 -2
- package/dist/entry.d.ts.map +0 -1
- package/dist/entry.js.map +0 -1
- package/dist/htree-path.d.ts +0 -13
- package/dist/htree-path.d.ts.map +0 -1
- package/dist/htree-path.js +0 -38
- package/dist/htree-path.js.map +0 -1
- package/dist/htree-url.d.ts +0 -22
- package/dist/htree-url.d.ts.map +0 -1
- package/dist/htree-url.js +0 -118
- package/dist/htree-url.js.map +0 -1
- package/dist/index.d.ts +0 -17
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -8
- package/dist/index.js.map +0 -1
- package/dist/mediaStreaming.d.ts +0 -7
- package/dist/mediaStreaming.d.ts.map +0 -1
- package/dist/mediaStreaming.js +0 -48
- package/dist/mediaStreaming.js.map +0 -1
- package/dist/p2p/boundedQueue.d.ts +0 -79
- package/dist/p2p/boundedQueue.d.ts.map +0 -1
- package/dist/p2p/boundedQueue.js +0 -134
- package/dist/p2p/boundedQueue.js.map +0 -1
- package/dist/p2p/errorMessage.d.ts +0 -5
- package/dist/p2p/errorMessage.d.ts.map +0 -1
- package/dist/p2p/errorMessage.js +0 -7
- package/dist/p2p/errorMessage.js.map +0 -1
- package/dist/p2p/index.d.ts +0 -8
- package/dist/p2p/index.d.ts.map +0 -1
- package/dist/p2p/index.js +0 -6
- package/dist/p2p/index.js.map +0 -1
- package/dist/p2p/lruCache.d.ts +0 -26
- package/dist/p2p/lruCache.d.ts.map +0 -1
- package/dist/p2p/lruCache.js +0 -65
- package/dist/p2p/lruCache.js.map +0 -1
- package/dist/p2p/meshQueryRouter.d.ts +0 -57
- package/dist/p2p/meshQueryRouter.d.ts.map +0 -1
- package/dist/p2p/meshQueryRouter.js +0 -264
- package/dist/p2p/meshQueryRouter.js.map +0 -1
- package/dist/p2p/protocol.d.ts +0 -10
- package/dist/p2p/protocol.d.ts.map +0 -1
- package/dist/p2p/protocol.js +0 -2
- package/dist/p2p/protocol.js.map +0 -1
- package/dist/p2p/queryForwardingMachine.d.ts +0 -46
- package/dist/p2p/queryForwardingMachine.d.ts.map +0 -1
- package/dist/p2p/queryForwardingMachine.js +0 -144
- package/dist/p2p/queryForwardingMachine.js.map +0 -1
- package/dist/p2p/signaling.d.ts +0 -63
- package/dist/p2p/signaling.d.ts.map +0 -1
- package/dist/p2p/signaling.js +0 -185
- package/dist/p2p/signaling.js.map +0 -1
- package/dist/p2p/uploadRateLimiter.d.ts +0 -21
- package/dist/p2p/uploadRateLimiter.d.ts.map +0 -1
- package/dist/p2p/uploadRateLimiter.js +0 -62
- package/dist/p2p/uploadRateLimiter.js.map +0 -1
- package/dist/p2p/webrtcController.d.ts +0 -176
- package/dist/p2p/webrtcController.d.ts.map +0 -1
- package/dist/p2p/webrtcController.js +0 -938
- package/dist/p2p/webrtcController.js.map +0 -1
- package/dist/p2p/webrtcProxy.d.ts +0 -62
- package/dist/p2p/webrtcProxy.d.ts.map +0 -1
- package/dist/p2p/webrtcProxy.js +0 -447
- package/dist/p2p/webrtcProxy.js.map +0 -1
- package/dist/privacyGuards.d.ts +0 -14
- package/dist/privacyGuards.d.ts.map +0 -1
- package/dist/privacyGuards.js +0 -27
- package/dist/privacyGuards.js.map +0 -1
- package/dist/protocol.d.ts +0 -225
- package/dist/protocol.d.ts.map +0 -1
- package/dist/protocol.js +0 -2
- package/dist/protocol.js.map +0 -1
- package/dist/relay/identity.d.ts +0 -36
- package/dist/relay/identity.d.ts.map +0 -1
- package/dist/relay/identity.js +0 -78
- package/dist/relay/identity.js.map +0 -1
- package/dist/relay/mediaHandler.d.ts +0 -64
- package/dist/relay/mediaHandler.d.ts.map +0 -1
- package/dist/relay/mediaHandler.js +0 -1285
- package/dist/relay/mediaHandler.js.map +0 -1
- package/dist/relay/ndk.d.ts +0 -96
- package/dist/relay/ndk.d.ts.map +0 -1
- package/dist/relay/ndk.js +0 -502
- package/dist/relay/ndk.js.map +0 -1
- package/dist/relay/nostr-wasm.d.ts +0 -14
- package/dist/relay/nostr-wasm.d.ts.map +0 -1
- package/dist/relay/nostr-wasm.js.map +0 -1
- package/dist/relay/nostr.d.ts +0 -60
- package/dist/relay/nostr.d.ts.map +0 -1
- package/dist/relay/nostr.js +0 -207
- package/dist/relay/nostr.js.map +0 -1
- package/dist/relay/protocol.d.ts +0 -592
- package/dist/relay/protocol.d.ts.map +0 -1
- package/dist/relay/protocol.js +0 -16
- package/dist/relay/protocol.js.map +0 -1
- package/dist/relay/publicAssetUrl.d.ts +0 -6
- package/dist/relay/publicAssetUrl.d.ts.map +0 -1
- package/dist/relay/publicAssetUrl.js +0 -14
- package/dist/relay/publicAssetUrl.js.map +0 -1
- package/dist/relay/rootPathResolver.d.ts +0 -9
- package/dist/relay/rootPathResolver.d.ts.map +0 -1
- package/dist/relay/rootPathResolver.js +0 -32
- package/dist/relay/rootPathResolver.js.map +0 -1
- package/dist/relay/signing.d.ts +0 -50
- package/dist/relay/signing.d.ts.map +0 -1
- package/dist/relay/signing.js +0 -299
- package/dist/relay/signing.js.map +0 -1
- package/dist/relay/treeRootCache.d.ts +0 -86
- package/dist/relay/treeRootCache.d.ts.map +0 -1
- package/dist/relay/treeRootCache.js +0 -269
- package/dist/relay/treeRootCache.js.map +0 -1
- package/dist/relay/treeRootSubscription.d.ts +0 -55
- package/dist/relay/treeRootSubscription.d.ts.map +0 -1
- package/dist/relay/treeRootSubscription.js +0 -478
- package/dist/relay/treeRootSubscription.js.map +0 -1
- package/dist/relay/utils/constants.d.ts +0 -76
- package/dist/relay/utils/constants.d.ts.map +0 -1
- package/dist/relay/utils/constants.js +0 -113
- package/dist/relay/utils/constants.js.map +0 -1
- package/dist/relay/utils/errorMessage.d.ts +0 -5
- package/dist/relay/utils/errorMessage.d.ts.map +0 -1
- package/dist/relay/utils/errorMessage.js +0 -8
- package/dist/relay/utils/errorMessage.js.map +0 -1
- package/dist/relay/utils/lruCache.d.ts +0 -26
- package/dist/relay/utils/lruCache.d.ts.map +0 -1
- package/dist/relay/utils/lruCache.js +0 -66
- package/dist/relay/utils/lruCache.js.map +0 -1
- package/dist/relay/webrtc.d.ts +0 -2
- package/dist/relay/webrtc.d.ts.map +0 -1
- package/dist/relay/webrtc.js +0 -3
- package/dist/relay/webrtc.js.map +0 -1
- package/dist/relay/webrtcSignaling.d.ts +0 -37
- package/dist/relay/webrtcSignaling.d.ts.map +0 -1
- package/dist/relay/webrtcSignaling.js +0 -86
- package/dist/relay/webrtcSignaling.js.map +0 -1
- package/dist/relay/worker.d.ts +0 -12
- package/dist/relay/worker.d.ts.map +0 -1
- package/dist/relay/worker.js +0 -1540
- package/dist/relay/worker.js.map +0 -1
- package/dist/relay-client.d.ts +0 -31
- package/dist/relay-client.d.ts.map +0 -1
- package/dist/relay-client.js +0 -197
- package/dist/relay-client.js.map +0 -1
- package/dist/relay-entry.d.ts +0 -2
- package/dist/relay-entry.d.ts.map +0 -1
- package/dist/relay-entry.js +0 -2
- package/dist/relay-entry.js.map +0 -1
- package/dist/runtime-network.d.ts +0 -23
- package/dist/runtime-network.d.ts.map +0 -1
- package/dist/runtime-network.js +0 -105
- package/dist/runtime-network.js.map +0 -1
- package/dist/runtime.d.ts +0 -24
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js +0 -126
- package/dist/runtime.js.map +0 -1
- package/dist/transferableBytes.d.ts +0 -2
- package/dist/transferableBytes.d.ts.map +0 -1
- package/dist/transferableBytes.js.map +0 -1
- package/dist/tree-root.d.ts +0 -201
- package/dist/tree-root.d.ts.map +0 -1
- package/dist/tree-root.js +0 -632
- package/dist/tree-root.js.map +0 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/worker.d.ts +0 -9
- package/dist/worker.d.ts.map +0 -1
- package/dist/worker.js +0 -792
- package/dist/worker.js.map +0 -1
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebRTC Proxy
|
|
3
|
+
*
|
|
4
|
+
* Thin transport layer that manages RTCPeerConnection in main thread.
|
|
5
|
+
* Worker controls all logic - this just executes commands and reports events.
|
|
6
|
+
*
|
|
7
|
+
* Main thread owns RTCPeerConnection because it's not available in workers.
|
|
8
|
+
* See: https://github.com/w3c/webrtc-extensions/issues/77
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { WebRTCCommand, WebRTCEvent } from './protocol.js';
|
|
12
|
+
import { BoundedQueue } from './boundedQueue.js';
|
|
13
|
+
import { getErrorMessage } from './errorMessage.js';
|
|
14
|
+
import { UploadRateLimiter } from './uploadRateLimiter.js';
|
|
15
|
+
|
|
16
|
+
const REQUEST_MESSAGE_TYPE = 0x00;
|
|
17
|
+
|
|
18
|
+
const isTestMode = typeof globalThis !== 'undefined' &&
|
|
19
|
+
Boolean((globalThis as { __HTREE_P2P_TEST_MODE__?: boolean }).__HTREE_P2P_TEST_MODE__);
|
|
20
|
+
const ICE_SERVERS: RTCIceServer[] = isTestMode
|
|
21
|
+
? []
|
|
22
|
+
: [
|
|
23
|
+
{ urls: 'stun:stun.l.google.com:19302' },
|
|
24
|
+
{ urls: 'stun:stun.cloudflare.com:3478' },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
interface PeerConnection {
|
|
28
|
+
pc: RTCPeerConnection;
|
|
29
|
+
dataChannel: RTCDataChannel | null;
|
|
30
|
+
pubkey: string;
|
|
31
|
+
pendingCandidates: RTCIceCandidateInit[];
|
|
32
|
+
sendQueue: BoundedQueue<Uint8Array>;
|
|
33
|
+
sending: boolean;
|
|
34
|
+
bufferHighSignaled: boolean; // Track if we've signaled high buffer to worker
|
|
35
|
+
drainTimeoutId: ReturnType<typeof setTimeout> | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type EventCallback = (event: WebRTCEvent) => void;
|
|
39
|
+
type WebRTCProxyConfig = {
|
|
40
|
+
maxUploadBytesPerSecond?: number | null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export class WebRTCProxy {
|
|
44
|
+
private peers = new Map<string, PeerConnection>();
|
|
45
|
+
private onEvent: EventCallback;
|
|
46
|
+
private readonly uploadRateLimiter: UploadRateLimiter;
|
|
47
|
+
|
|
48
|
+
// Queue limits to prevent memory blowup on slow/stalled connections
|
|
49
|
+
private static readonly MAX_QUEUE_BYTES = 8 * 1024 * 1024; // 8MB per peer
|
|
50
|
+
private static readonly MAX_QUEUE_ITEMS = 100;
|
|
51
|
+
|
|
52
|
+
constructor(onEvent: EventCallback, config: WebRTCProxyConfig = {}) {
|
|
53
|
+
this.onEvent = onEvent;
|
|
54
|
+
this.uploadRateLimiter = new UploadRateLimiter({
|
|
55
|
+
bytesPerSecond: config.maxUploadBytesPerSecond,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private createSendQueue(peerId: string): BoundedQueue<Uint8Array> {
|
|
60
|
+
return new BoundedQueue<Uint8Array>({
|
|
61
|
+
maxItems: WebRTCProxy.MAX_QUEUE_ITEMS,
|
|
62
|
+
maxBytes: WebRTCProxy.MAX_QUEUE_BYTES,
|
|
63
|
+
getBytes: (item) => item.byteLength,
|
|
64
|
+
onDrop: (item) => {
|
|
65
|
+
console.warn(`[WebRTCProxy] Queue overflow for ${peerId.slice(0, 8)}, dropped ${item.byteLength}B`);
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle command from worker
|
|
72
|
+
*/
|
|
73
|
+
handleCommand(cmd: WebRTCCommand): void {
|
|
74
|
+
switch (cmd.type) {
|
|
75
|
+
case 'rtc:createPeer':
|
|
76
|
+
this.createPeer(cmd.peerId, cmd.pubkey);
|
|
77
|
+
break;
|
|
78
|
+
case 'rtc:closePeer':
|
|
79
|
+
this.closePeer(cmd.peerId);
|
|
80
|
+
break;
|
|
81
|
+
case 'rtc:createOffer':
|
|
82
|
+
this.createOffer(cmd.peerId);
|
|
83
|
+
break;
|
|
84
|
+
case 'rtc:createAnswer':
|
|
85
|
+
this.createAnswer(cmd.peerId);
|
|
86
|
+
break;
|
|
87
|
+
case 'rtc:setLocalDescription':
|
|
88
|
+
this.setLocalDescription(cmd.peerId, cmd.sdp);
|
|
89
|
+
break;
|
|
90
|
+
case 'rtc:setRemoteDescription':
|
|
91
|
+
this.setRemoteDescription(cmd.peerId, cmd.sdp);
|
|
92
|
+
break;
|
|
93
|
+
case 'rtc:addIceCandidate':
|
|
94
|
+
this.addIceCandidate(cmd.peerId, cmd.candidate);
|
|
95
|
+
break;
|
|
96
|
+
case 'rtc:sendData':
|
|
97
|
+
this.sendData(cmd.peerId, cmd.data);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private createPeer(peerId: string, pubkey: string): void {
|
|
103
|
+
// Clean up existing if present
|
|
104
|
+
if (this.peers.has(peerId)) {
|
|
105
|
+
this.closePeer(peerId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
|
|
109
|
+
|
|
110
|
+
const peer: PeerConnection = {
|
|
111
|
+
pc,
|
|
112
|
+
dataChannel: null,
|
|
113
|
+
pubkey,
|
|
114
|
+
pendingCandidates: [],
|
|
115
|
+
sendQueue: this.createSendQueue(peerId),
|
|
116
|
+
sending: false,
|
|
117
|
+
bufferHighSignaled: false,
|
|
118
|
+
drainTimeoutId: null,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Create data channel (offerer creates, answerer receives via ondatachannel)
|
|
122
|
+
const dc = pc.createDataChannel('hashtree', {
|
|
123
|
+
ordered: true,
|
|
124
|
+
});
|
|
125
|
+
this.setupDataChannel(peerId, dc);
|
|
126
|
+
peer.dataChannel = dc;
|
|
127
|
+
|
|
128
|
+
// Handle incoming data channel (for answerer)
|
|
129
|
+
pc.ondatachannel = (event) => {
|
|
130
|
+
this.setupDataChannel(peerId, event.channel);
|
|
131
|
+
peer.dataChannel = event.channel;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// ICE candidate gathering
|
|
135
|
+
pc.onicecandidate = (event) => {
|
|
136
|
+
this.onEvent({
|
|
137
|
+
type: 'rtc:iceCandidate',
|
|
138
|
+
peerId,
|
|
139
|
+
candidate: event.candidate?.toJSON() ?? null,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (!event.candidate) {
|
|
143
|
+
this.onEvent({ type: 'rtc:iceGatheringComplete', peerId });
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Connection state changes
|
|
148
|
+
pc.onconnectionstatechange = () => {
|
|
149
|
+
this.onEvent({
|
|
150
|
+
type: 'rtc:peerStateChange',
|
|
151
|
+
peerId,
|
|
152
|
+
state: pc.connectionState,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (pc.connectionState === 'closed' || pc.connectionState === 'failed') {
|
|
156
|
+
this.cleanupPeer(peerId);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
this.peers.set(peerId, peer);
|
|
161
|
+
this.onEvent({ type: 'rtc:peerCreated', peerId });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private setupDataChannel(peerId: string, dc: RTCDataChannel): void {
|
|
165
|
+
dc.binaryType = 'arraybuffer';
|
|
166
|
+
|
|
167
|
+
dc.onopen = () => {
|
|
168
|
+
this.onEvent({ type: 'rtc:dataChannelOpen', peerId });
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// If channel is already open (can happen with ondatachannel), fire event immediately
|
|
172
|
+
if (dc.readyState === 'open') {
|
|
173
|
+
this.onEvent({ type: 'rtc:dataChannelOpen', peerId });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
dc.onclose = () => {
|
|
177
|
+
this.onEvent({ type: 'rtc:dataChannelClose', peerId });
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
dc.onerror = (event) => {
|
|
181
|
+
const errorEvent = event as RTCErrorEvent;
|
|
182
|
+
this.onEvent({
|
|
183
|
+
type: 'rtc:dataChannelError',
|
|
184
|
+
peerId,
|
|
185
|
+
error: errorEvent.error?.message || 'Unknown error',
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
dc.onmessage = (event) => {
|
|
190
|
+
const data = event.data instanceof ArrayBuffer
|
|
191
|
+
? new Uint8Array(event.data)
|
|
192
|
+
: new Uint8Array(0);
|
|
193
|
+
|
|
194
|
+
this.onEvent({
|
|
195
|
+
type: 'rtc:dataChannelMessage',
|
|
196
|
+
peerId,
|
|
197
|
+
data,
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private async createOffer(peerId: string): Promise<void> {
|
|
203
|
+
const peer = this.peers.get(peerId);
|
|
204
|
+
if (!peer) return;
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const offer = await peer.pc.createOffer();
|
|
208
|
+
this.onEvent({
|
|
209
|
+
type: 'rtc:offerCreated',
|
|
210
|
+
peerId,
|
|
211
|
+
sdp: offer,
|
|
212
|
+
});
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.error('[WebRTCProxy] Failed to create offer:', err);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private async createAnswer(peerId: string): Promise<void> {
|
|
219
|
+
const peer = this.peers.get(peerId);
|
|
220
|
+
if (!peer) return;
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const answer = await peer.pc.createAnswer();
|
|
224
|
+
this.onEvent({
|
|
225
|
+
type: 'rtc:answerCreated',
|
|
226
|
+
peerId,
|
|
227
|
+
sdp: answer,
|
|
228
|
+
});
|
|
229
|
+
} catch (err) {
|
|
230
|
+
console.error('[WebRTCProxy] Failed to create answer:', err);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private async setLocalDescription(peerId: string, sdp: RTCSessionDescriptionInit): Promise<void> {
|
|
235
|
+
const peer = this.peers.get(peerId);
|
|
236
|
+
if (!peer) return;
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
await peer.pc.setLocalDescription(sdp);
|
|
240
|
+
this.onEvent({ type: 'rtc:descriptionSet', peerId });
|
|
241
|
+
} catch (err) {
|
|
242
|
+
this.onEvent({
|
|
243
|
+
type: 'rtc:descriptionSet',
|
|
244
|
+
peerId,
|
|
245
|
+
error: getErrorMessage(err),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private async setRemoteDescription(peerId: string, sdp: RTCSessionDescriptionInit): Promise<void> {
|
|
251
|
+
const peer = this.peers.get(peerId);
|
|
252
|
+
if (!peer) return;
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
if (sdp.type === 'offer'
|
|
256
|
+
&& peer.pc.signalingState !== 'stable'
|
|
257
|
+
&& peer.pc.signalingState !== 'closed') {
|
|
258
|
+
await peer.pc.setLocalDescription({ type: 'rollback' });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
await peer.pc.setRemoteDescription(sdp);
|
|
262
|
+
|
|
263
|
+
// Apply any pending ICE candidates
|
|
264
|
+
for (const candidate of peer.pendingCandidates) {
|
|
265
|
+
await peer.pc.addIceCandidate(candidate);
|
|
266
|
+
}
|
|
267
|
+
peer.pendingCandidates = [];
|
|
268
|
+
|
|
269
|
+
this.onEvent({ type: 'rtc:descriptionSet', peerId });
|
|
270
|
+
} catch (err) {
|
|
271
|
+
this.onEvent({
|
|
272
|
+
type: 'rtc:descriptionSet',
|
|
273
|
+
peerId,
|
|
274
|
+
error: getErrorMessage(err),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private async addIceCandidate(peerId: string, candidate: RTCIceCandidateInit): Promise<void> {
|
|
280
|
+
const peer = this.peers.get(peerId);
|
|
281
|
+
if (!peer) return;
|
|
282
|
+
|
|
283
|
+
// Queue if remote description not set yet
|
|
284
|
+
if (!peer.pc.remoteDescription) {
|
|
285
|
+
peer.pendingCandidates.push(candidate);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
await peer.pc.addIceCandidate(candidate);
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.error('[WebRTCProxy] Failed to add ICE candidate:', err);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 256KB threshold - pause sending when buffer exceeds this
|
|
297
|
+
private static readonly BUFFER_THRESHOLD = 256 * 1024;
|
|
298
|
+
// 4MB threshold for sendQueue - signal worker to pause when exceeded
|
|
299
|
+
private static readonly QUEUE_HIGH_THRESHOLD = 4 * 1024 * 1024;
|
|
300
|
+
// 1MB threshold for sendQueue - signal worker to resume when below
|
|
301
|
+
private static readonly QUEUE_LOW_THRESHOLD = 1 * 1024 * 1024;
|
|
302
|
+
|
|
303
|
+
private getQueueSize(peer: PeerConnection): number {
|
|
304
|
+
return peer.sendQueue.bytes;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private isPriorityDataMessage(data: Uint8Array): boolean {
|
|
308
|
+
return data.byteLength > 0 && data[0] === REQUEST_MESSAGE_TYPE;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private sendData(peerId: string, data: Uint8Array): void {
|
|
312
|
+
const peer = this.peers.get(peerId);
|
|
313
|
+
if (!peer?.dataChannel || peer.dataChannel.readyState !== 'open') {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Small request frames should overtake bulky response traffic so cache misses
|
|
318
|
+
// are not starved by background uploads on the same peer connection.
|
|
319
|
+
if (this.isPriorityDataMessage(data)) {
|
|
320
|
+
peer.sendQueue.unshift(data);
|
|
321
|
+
} else {
|
|
322
|
+
peer.sendQueue.push(data);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Check if queue is getting too large - signal worker to slow down
|
|
326
|
+
const queueSize = this.getQueueSize(peer);
|
|
327
|
+
if (!peer.bufferHighSignaled && queueSize > WebRTCProxy.QUEUE_HIGH_THRESHOLD) {
|
|
328
|
+
peer.bufferHighSignaled = true;
|
|
329
|
+
this.onEvent({ type: 'rtc:bufferHigh', peerId });
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Start draining if not already
|
|
333
|
+
if (!peer.sending) {
|
|
334
|
+
this.drainQueue(peerId);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private drainQueue(peerId: string): void {
|
|
339
|
+
const peer = this.peers.get(peerId);
|
|
340
|
+
if (!peer?.dataChannel || peer.dataChannel.readyState !== 'open') {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (peer.drainTimeoutId) {
|
|
345
|
+
clearTimeout(peer.drainTimeoutId);
|
|
346
|
+
peer.drainTimeoutId = null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
peer.sending = true;
|
|
350
|
+
const dc = peer.dataChannel;
|
|
351
|
+
|
|
352
|
+
// Send as much as we can without overflowing the buffer
|
|
353
|
+
while (!peer.sendQueue.isEmpty && dc.bufferedAmount < WebRTCProxy.BUFFER_THRESHOLD) {
|
|
354
|
+
const nextData = peer.sendQueue.peek();
|
|
355
|
+
if (!nextData) {
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const reservation = this.uploadRateLimiter.reserve(nextData.byteLength);
|
|
360
|
+
if (!reservation.allowed) {
|
|
361
|
+
peer.sending = false;
|
|
362
|
+
this.scheduleRateLimitedDrain(peerId, reservation.delayMs);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const data = peer.sendQueue.shift()!;
|
|
367
|
+
try {
|
|
368
|
+
const payload = data.byteOffset === 0 && data.byteLength === data.buffer.byteLength
|
|
369
|
+
? data.buffer
|
|
370
|
+
: data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
371
|
+
dc.send(payload as ArrayBuffer);
|
|
372
|
+
} catch {
|
|
373
|
+
// Drop on failure instead of infinite re-queue (prevents memory blowup)
|
|
374
|
+
console.warn(`[WebRTCProxy] Send failed for ${peerId.slice(0, 8)}, dropped ${data.byteLength}B`);
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Check if queue has drained enough to signal worker to resume
|
|
380
|
+
if (peer.bufferHighSignaled) {
|
|
381
|
+
const queueSize = this.getQueueSize(peer);
|
|
382
|
+
if (queueSize < WebRTCProxy.QUEUE_LOW_THRESHOLD) {
|
|
383
|
+
peer.bufferHighSignaled = false;
|
|
384
|
+
this.onEvent({ type: 'rtc:bufferLow', peerId });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// If there's more to send, wait for buffer to drain
|
|
389
|
+
if (!peer.sendQueue.isEmpty) {
|
|
390
|
+
dc.bufferedAmountLowThreshold = WebRTCProxy.BUFFER_THRESHOLD / 2;
|
|
391
|
+
dc.onbufferedamountlow = () => {
|
|
392
|
+
dc.onbufferedamountlow = null;
|
|
393
|
+
this.drainQueue(peerId);
|
|
394
|
+
};
|
|
395
|
+
} else {
|
|
396
|
+
peer.sending = false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private closePeer(peerId: string): void {
|
|
401
|
+
const peer = this.peers.get(peerId);
|
|
402
|
+
if (!peer) return;
|
|
403
|
+
|
|
404
|
+
this.cleanupPeer(peerId);
|
|
405
|
+
this.onEvent({ type: 'rtc:peerClosed', peerId });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private cleanupPeer(peerId: string): void {
|
|
409
|
+
const peer = this.peers.get(peerId);
|
|
410
|
+
if (!peer) return;
|
|
411
|
+
|
|
412
|
+
// Clear send queue
|
|
413
|
+
peer.sendQueue.clear();
|
|
414
|
+
peer.sending = false;
|
|
415
|
+
if (peer.drainTimeoutId) {
|
|
416
|
+
clearTimeout(peer.drainTimeoutId);
|
|
417
|
+
peer.drainTimeoutId = null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Close data channel
|
|
421
|
+
if (peer.dataChannel) {
|
|
422
|
+
peer.dataChannel.onopen = null;
|
|
423
|
+
peer.dataChannel.onclose = null;
|
|
424
|
+
peer.dataChannel.onerror = null;
|
|
425
|
+
peer.dataChannel.onmessage = null;
|
|
426
|
+
peer.dataChannel.onbufferedamountlow = null;
|
|
427
|
+
peer.dataChannel.close();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Close peer connection
|
|
431
|
+
peer.pc.onicecandidate = null;
|
|
432
|
+
peer.pc.ondatachannel = null;
|
|
433
|
+
peer.pc.onconnectionstatechange = null;
|
|
434
|
+
peer.pc.close();
|
|
435
|
+
|
|
436
|
+
this.peers.delete(peerId);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Close all connections
|
|
441
|
+
*/
|
|
442
|
+
close(): void {
|
|
443
|
+
for (const peerId of this.peers.keys()) {
|
|
444
|
+
this.closePeer(peerId);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Get connected peer count
|
|
450
|
+
*/
|
|
451
|
+
getConnectedCount(): number {
|
|
452
|
+
let count = 0;
|
|
453
|
+
for (const peer of this.peers.values()) {
|
|
454
|
+
if (peer.pc.connectionState === 'connected' &&
|
|
455
|
+
peer.dataChannel?.readyState === 'open') {
|
|
456
|
+
count++;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return count;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Get all peer IDs
|
|
464
|
+
*/
|
|
465
|
+
getPeerIds(): string[] {
|
|
466
|
+
return Array.from(this.peers.keys());
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
setUploadLimitBytesPerSecond(maxUploadBytesPerSecond?: number | null): void {
|
|
470
|
+
this.uploadRateLimiter.setBytesPerSecond(maxUploadBytesPerSecond);
|
|
471
|
+
for (const peerId of this.peers.keys()) {
|
|
472
|
+
const peer = this.peers.get(peerId);
|
|
473
|
+
if (!peer || peer.sendQueue.isEmpty) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (!peer.sending) {
|
|
477
|
+
this.drainQueue(peerId);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
private scheduleRateLimitedDrain(peerId: string, delayMs: number): void {
|
|
483
|
+
const peer = this.peers.get(peerId);
|
|
484
|
+
if (!peer || peer.drainTimeoutId) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
peer.drainTimeoutId = setTimeout(() => {
|
|
489
|
+
const activePeer = this.peers.get(peerId);
|
|
490
|
+
if (!activePeer) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
activePeer.drainTimeoutId = null;
|
|
494
|
+
this.drainQueue(peerId);
|
|
495
|
+
}, delayMs);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Singleton instance
|
|
500
|
+
let instance: WebRTCProxy | null = null;
|
|
501
|
+
|
|
502
|
+
export function initWebRTCProxy(onEvent: EventCallback): WebRTCProxy {
|
|
503
|
+
if (instance) {
|
|
504
|
+
instance.close();
|
|
505
|
+
}
|
|
506
|
+
instance = new WebRTCProxy(onEvent);
|
|
507
|
+
return instance;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export function getWebRTCProxy(): WebRTCProxy | null {
|
|
511
|
+
return instance;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export function closeWebRTCProxy(): void {
|
|
515
|
+
if (instance) {
|
|
516
|
+
instance.close();
|
|
517
|
+
instance = null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CID } from '@hashtree/core';
|
|
2
|
+
|
|
3
|
+
const ENCRYPTION_KEY_BYTES = 32;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Uploads must always point to encrypted content CIDs.
|
|
7
|
+
*/
|
|
8
|
+
export function assertEncryptedUploadCid(cid: CID): void {
|
|
9
|
+
if (!cid.key) {
|
|
10
|
+
throw new Error('Refusing to upload unencrypted CID');
|
|
11
|
+
}
|
|
12
|
+
if (cid.key.length !== ENCRYPTION_KEY_BYTES) {
|
|
13
|
+
throw new Error('Refusing to upload CID with invalid encryption key');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Mark known-encrypted block hashes as safe for peer serving.
|
|
19
|
+
*/
|
|
20
|
+
export function markEncryptedHashes(hashes: Iterable<string>, allowlist: Set<string>): void {
|
|
21
|
+
for (const hashHex of hashes) {
|
|
22
|
+
allowlist.add(hashHex.toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Peer responses are restricted to hashes explicitly marked as encrypted.
|
|
28
|
+
*/
|
|
29
|
+
export function shouldServeHashToPeer(hashHex: string, allowlist: ReadonlySet<string>): boolean {
|
|
30
|
+
return allowlist.has(hashHex.toLowerCase());
|
|
31
|
+
}
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { CID } from '@hashtree/core';
|
|
2
|
+
|
|
3
|
+
export interface BlossomServerConfig {
|
|
4
|
+
url: string;
|
|
5
|
+
read?: boolean;
|
|
6
|
+
write?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface WorkerConfig {
|
|
10
|
+
storeName?: string;
|
|
11
|
+
blossomServers?: BlossomServerConfig[];
|
|
12
|
+
relays?: string[];
|
|
13
|
+
storageMaxBytes?: number;
|
|
14
|
+
connectivityProbeIntervalMs?: number;
|
|
15
|
+
diagnosticsEnabled?: boolean;
|
|
16
|
+
diagnosticsMirrorToConsole?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type WorkerDiagnosticLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
20
|
+
export type WorkerDiagnosticDataValue = string | number | boolean | null;
|
|
21
|
+
|
|
22
|
+
export interface WorkerDiagnosticEvent {
|
|
23
|
+
scope: string;
|
|
24
|
+
code: string;
|
|
25
|
+
level: WorkerDiagnosticLevel;
|
|
26
|
+
message: string;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
data?: Record<string, WorkerDiagnosticDataValue>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ConnectivityState {
|
|
32
|
+
online: boolean;
|
|
33
|
+
reachableReadServers: number;
|
|
34
|
+
totalReadServers: number;
|
|
35
|
+
reachableWriteServers: number;
|
|
36
|
+
totalWriteServers: number;
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type BlobSource = 'idb' | 'blossom' | 'p2p';
|
|
41
|
+
|
|
42
|
+
export interface UploadServerStatus {
|
|
43
|
+
url: string;
|
|
44
|
+
uploaded: number;
|
|
45
|
+
skipped: number;
|
|
46
|
+
failed: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface UploadProgressState {
|
|
50
|
+
hashHex: string;
|
|
51
|
+
nhash: string;
|
|
52
|
+
totalServers: number;
|
|
53
|
+
processedServers: number;
|
|
54
|
+
uploadedServers: number;
|
|
55
|
+
skippedServers: number;
|
|
56
|
+
failedServers: number;
|
|
57
|
+
totalChunks?: number;
|
|
58
|
+
processedChunks?: number;
|
|
59
|
+
/** 0..1 normalized progress for chunk upload traversal */
|
|
60
|
+
progressRatio?: number;
|
|
61
|
+
serverStatuses?: UploadServerStatus[];
|
|
62
|
+
complete: boolean;
|
|
63
|
+
error?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface BlossomBandwidthServerStats {
|
|
67
|
+
url: string;
|
|
68
|
+
bytesSent: number;
|
|
69
|
+
bytesReceived: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface BlossomBandwidthState {
|
|
73
|
+
totalBytesSent: number;
|
|
74
|
+
totalBytesReceived: number;
|
|
75
|
+
updatedAt: number;
|
|
76
|
+
servers: BlossomBandwidthServerStats[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface BlobStreamStarted {
|
|
80
|
+
id: string;
|
|
81
|
+
streamId: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface RootResolveOptions {
|
|
85
|
+
timeoutMs?: number;
|
|
86
|
+
settleMs?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type WorkerRequest =
|
|
90
|
+
| { type: 'init'; id: string; config: WorkerConfig }
|
|
91
|
+
| { type: 'close'; id: string }
|
|
92
|
+
| { type: 'putBlob'; id: string; data: Uint8Array; mimeType?: string; upload?: boolean }
|
|
93
|
+
| { type: 'beginPutBlobStream'; id: string; mimeType?: string; upload?: boolean }
|
|
94
|
+
| { type: 'appendPutBlobStream'; id: string; streamId: string; chunk: Uint8Array }
|
|
95
|
+
| { type: 'finishPutBlobStream'; id: string; streamId: string }
|
|
96
|
+
| { type: 'cancelPutBlobStream'; id: string; streamId: string }
|
|
97
|
+
| { type: 'p2pFetchResult'; id: string; requestId: string; data?: Uint8Array; error?: string }
|
|
98
|
+
| { type: 'getBlob'; id: string; hashHex: string; forPeer?: boolean }
|
|
99
|
+
| { type: 'registerMediaPort'; id: string; port: MessagePort }
|
|
100
|
+
| { type: 'setBlossomServers'; id: string; servers: BlossomServerConfig[] }
|
|
101
|
+
| { type: 'setStorageMaxBytes'; id: string; maxBytes: number }
|
|
102
|
+
| { type: 'getStorageStats'; id: string }
|
|
103
|
+
| { type: 'probeConnectivity'; id: string }
|
|
104
|
+
| { type: 'resolveRoot'; id: string; npub: string; path?: string; timeoutMs?: number; settleMs?: number }
|
|
105
|
+
| { type: 'watchRoot'; id: string; npub: string; path?: string; timeoutMs?: number; settleMs?: number }
|
|
106
|
+
| { type: 'unwatchRoot'; id: string; watchId: string };
|
|
107
|
+
|
|
108
|
+
export type WorkerResponse =
|
|
109
|
+
| { type: 'ready'; id: string }
|
|
110
|
+
| { type: 'error'; id?: string; error: string }
|
|
111
|
+
| { type: 'diagnostic'; event: WorkerDiagnosticEvent }
|
|
112
|
+
| { type: 'p2pFetch'; requestId: string; hashHex: string }
|
|
113
|
+
| { type: 'blobStreamStarted'; id: string; streamId: string }
|
|
114
|
+
| { type: 'blobStored'; id: string; hashHex: string; nhash: string }
|
|
115
|
+
| { type: 'blob'; id: string; data?: Uint8Array; source?: BlobSource; error?: string }
|
|
116
|
+
| { type: 'cid'; id: string; cid?: CID; error?: string }
|
|
117
|
+
| { type: 'void'; id: string; error?: string }
|
|
118
|
+
| { type: 'storageStats'; id: string; items: number; bytes: number; maxBytes: number; error?: string }
|
|
119
|
+
| { type: 'connectivity'; id: string; state?: ConnectivityState; error?: string }
|
|
120
|
+
| { type: 'rootWatchStarted'; id: string; watchId: string; cid?: CID; error?: string }
|
|
121
|
+
| { type: 'rootUpdate'; watchId: string; cid?: CID }
|
|
122
|
+
| { type: 'connectivityUpdate'; state: ConnectivityState }
|
|
123
|
+
| { type: 'blossomBandwidth'; stats: BlossomBandwidthState }
|
|
124
|
+
| { type: 'uploadProgress'; progress: UploadProgressState };
|