@hashtree/worker 0.1.26 → 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/README.md +16 -16
- package/package.json +23 -19
- 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/iris/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/src/transferableBytes.ts +5 -0
- package/src/tree-root.ts +851 -0
- package/src/types.ts +8 -0
- package/src/worker.ts +975 -0
- package/LICENSE +0 -21
- 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 -144
- 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 -393
- 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/iris/identity.d.ts +0 -36
- package/dist/iris/identity.d.ts.map +0 -1
- package/dist/iris/identity.js +0 -78
- package/dist/iris/identity.js.map +0 -1
- package/dist/iris/mediaHandler.d.ts +0 -64
- package/dist/iris/mediaHandler.d.ts.map +0 -1
- package/dist/iris/mediaHandler.js +0 -1285
- package/dist/iris/mediaHandler.js.map +0 -1
- package/dist/iris/ndk.d.ts +0 -96
- package/dist/iris/ndk.d.ts.map +0 -1
- package/dist/iris/ndk.js +0 -502
- package/dist/iris/ndk.js.map +0 -1
- package/dist/iris/nostr-wasm.d.ts +0 -14
- package/dist/iris/nostr-wasm.d.ts.map +0 -1
- package/dist/iris/nostr-wasm.js.map +0 -1
- package/dist/iris/nostr.d.ts +0 -60
- package/dist/iris/nostr.d.ts.map +0 -1
- package/dist/iris/nostr.js +0 -207
- package/dist/iris/nostr.js.map +0 -1
- package/dist/iris/protocol.d.ts +0 -583
- package/dist/iris/protocol.d.ts.map +0 -1
- package/dist/iris/protocol.js +0 -16
- package/dist/iris/protocol.js.map +0 -1
- package/dist/iris/publicAssetUrl.d.ts +0 -6
- package/dist/iris/publicAssetUrl.d.ts.map +0 -1
- package/dist/iris/publicAssetUrl.js +0 -14
- package/dist/iris/publicAssetUrl.js.map +0 -1
- package/dist/iris/rootPathResolver.d.ts +0 -9
- package/dist/iris/rootPathResolver.d.ts.map +0 -1
- package/dist/iris/rootPathResolver.js +0 -32
- package/dist/iris/rootPathResolver.js.map +0 -1
- package/dist/iris/signing.d.ts +0 -50
- package/dist/iris/signing.d.ts.map +0 -1
- package/dist/iris/signing.js +0 -299
- package/dist/iris/signing.js.map +0 -1
- package/dist/iris/treeRootCache.d.ts +0 -86
- package/dist/iris/treeRootCache.d.ts.map +0 -1
- package/dist/iris/treeRootCache.js +0 -269
- package/dist/iris/treeRootCache.js.map +0 -1
- package/dist/iris/treeRootSubscription.d.ts +0 -55
- package/dist/iris/treeRootSubscription.d.ts.map +0 -1
- package/dist/iris/treeRootSubscription.js +0 -479
- package/dist/iris/treeRootSubscription.js.map +0 -1
- package/dist/iris/utils/constants.d.ts +0 -76
- package/dist/iris/utils/constants.d.ts.map +0 -1
- package/dist/iris/utils/constants.js +0 -113
- package/dist/iris/utils/constants.js.map +0 -1
- package/dist/iris/utils/errorMessage.d.ts +0 -5
- package/dist/iris/utils/errorMessage.d.ts.map +0 -1
- package/dist/iris/utils/errorMessage.js +0 -8
- package/dist/iris/utils/errorMessage.js.map +0 -1
- package/dist/iris/utils/lruCache.d.ts +0 -26
- package/dist/iris/utils/lruCache.d.ts.map +0 -1
- package/dist/iris/utils/lruCache.js +0 -66
- package/dist/iris/utils/lruCache.js.map +0 -1
- package/dist/iris/webrtc.d.ts +0 -2
- package/dist/iris/webrtc.d.ts.map +0 -1
- package/dist/iris/webrtc.js +0 -3
- package/dist/iris/webrtc.js.map +0 -1
- package/dist/iris/webrtcSignaling.d.ts +0 -37
- package/dist/iris/webrtcSignaling.d.ts.map +0 -1
- package/dist/iris/webrtcSignaling.js +0 -86
- package/dist/iris/webrtcSignaling.js.map +0 -1
- package/dist/iris/worker.d.ts +0 -12
- package/dist/iris/worker.d.ts.map +0 -1
- package/dist/iris/worker.js +0 -1529
- package/dist/iris/worker.js.map +0 -1
- package/dist/iris-client.d.ts +0 -31
- package/dist/iris-client.d.ts.map +0 -1
- package/dist/iris-client.js +0 -197
- package/dist/iris-client.js.map +0 -1
- package/dist/iris-entry.d.ts +0 -2
- package/dist/iris-entry.d.ts.map +0 -1
- package/dist/iris-entry.js +0 -2
- package/dist/iris-entry.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 -44
- package/dist/p2p/meshQueryRouter.d.ts.map +0 -1
- package/dist/p2p/meshQueryRouter.js +0 -228
- 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 -168
- package/dist/p2p/webrtcController.d.ts.map +0 -1
- package/dist/p2p/webrtcController.js +0 -902
- 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/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 -23
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js +0 -122
- package/dist/runtime.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 -797
- package/dist/worker.js.map +0 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BlossomStore,
|
|
3
|
+
type BlossomSigner,
|
|
4
|
+
type BlossomUploadCallback,
|
|
5
|
+
fromHex,
|
|
6
|
+
} from '@hashtree/core';
|
|
7
|
+
import { finalizeEvent, generateSecretKey } from 'nostr-tools/pure';
|
|
8
|
+
import type { BlossomServerConfig } from '../protocol.js';
|
|
9
|
+
import {
|
|
10
|
+
BlossomBandwidthTracker,
|
|
11
|
+
type BlossomBandwidthStats,
|
|
12
|
+
type BlossomBandwidthUpdateHandler,
|
|
13
|
+
} from './blossomBandwidthTracker.js';
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_BLOSSOM_SERVERS: BlossomServerConfig[] = [];
|
|
16
|
+
|
|
17
|
+
const MAX_CONCURRENT_READ_FETCHES = 12;
|
|
18
|
+
|
|
19
|
+
let activeReadFetches = 0;
|
|
20
|
+
const pendingReadFetchWaiters: Array<() => void> = [];
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
BlossomBandwidthServerStats,
|
|
24
|
+
BlossomBandwidthStats,
|
|
25
|
+
BlossomBandwidthUpdateHandler,
|
|
26
|
+
} from './blossomBandwidthTracker.js';
|
|
27
|
+
|
|
28
|
+
function normalizeServerUrl(url: string): string {
|
|
29
|
+
return url.replace(/\/+$/, '');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeServers(servers: BlossomServerConfig[] | undefined): BlossomServerConfig[] {
|
|
33
|
+
const source = servers && servers.length > 0 ? servers : DEFAULT_BLOSSOM_SERVERS;
|
|
34
|
+
const unique = new Map<string, BlossomServerConfig>();
|
|
35
|
+
for (const server of source) {
|
|
36
|
+
const url = normalizeServerUrl(server.url.trim());
|
|
37
|
+
if (!url) continue;
|
|
38
|
+
unique.set(url, {
|
|
39
|
+
url,
|
|
40
|
+
read: server.read ?? true,
|
|
41
|
+
write: server.write ?? false,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return Array.from(unique.values());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createEphemeralSigner(): BlossomSigner {
|
|
48
|
+
const secretKey = generateSecretKey();
|
|
49
|
+
return async (template) => {
|
|
50
|
+
const event = finalizeEvent({
|
|
51
|
+
...template,
|
|
52
|
+
kind: template.kind as 24242,
|
|
53
|
+
created_at: template.created_at,
|
|
54
|
+
content: template.content,
|
|
55
|
+
tags: template.tags,
|
|
56
|
+
}, secretKey);
|
|
57
|
+
return {
|
|
58
|
+
kind: event.kind,
|
|
59
|
+
created_at: event.created_at,
|
|
60
|
+
content: event.content,
|
|
61
|
+
tags: event.tags,
|
|
62
|
+
pubkey: event.pubkey,
|
|
63
|
+
id: event.id,
|
|
64
|
+
sig: event.sig,
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function releaseReadFetchSlot(): void {
|
|
70
|
+
activeReadFetches = Math.max(0, activeReadFetches - 1);
|
|
71
|
+
pendingReadFetchWaiters.shift()?.();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function withReadFetchSlot<T>(loader: () => Promise<T>): Promise<T> {
|
|
75
|
+
return new Promise<T>((resolve, reject) => {
|
|
76
|
+
const start = () => {
|
|
77
|
+
activeReadFetches += 1;
|
|
78
|
+
let pending: Promise<T>;
|
|
79
|
+
try {
|
|
80
|
+
pending = loader();
|
|
81
|
+
} catch (error) {
|
|
82
|
+
releaseReadFetchSlot();
|
|
83
|
+
reject(error);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pending
|
|
88
|
+
.then(resolve, reject)
|
|
89
|
+
.finally(() => {
|
|
90
|
+
releaseReadFetchSlot();
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (activeReadFetches < MAX_CONCURRENT_READ_FETCHES) {
|
|
95
|
+
start();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
pendingReadFetchWaiters.push(start);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export class BlossomTransport {
|
|
104
|
+
private servers: BlossomServerConfig[];
|
|
105
|
+
private readonly signer: BlossomSigner;
|
|
106
|
+
private readonly bandwidthTracker: BlossomBandwidthTracker;
|
|
107
|
+
private readonly inflightFetches = new Map<string, Promise<Uint8Array | null>>();
|
|
108
|
+
private store: BlossomStore;
|
|
109
|
+
|
|
110
|
+
constructor(servers?: BlossomServerConfig[], onBandwidthUpdate?: BlossomBandwidthUpdateHandler) {
|
|
111
|
+
this.servers = normalizeServers(servers);
|
|
112
|
+
this.signer = createEphemeralSigner();
|
|
113
|
+
this.bandwidthTracker = new BlossomBandwidthTracker(onBandwidthUpdate);
|
|
114
|
+
this.store = this.createStore(this.servers);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
setServers(servers: BlossomServerConfig[]): void {
|
|
118
|
+
this.servers = normalizeServers(servers);
|
|
119
|
+
this.store = this.createStore(this.servers);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getServers(): BlossomServerConfig[] {
|
|
123
|
+
return this.servers;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getWriteServers(): BlossomServerConfig[] {
|
|
127
|
+
return this.servers.filter(server => !!server.write);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getBandwidthStats(): BlossomBandwidthStats {
|
|
131
|
+
return this.bandwidthTracker.getStats();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private createStore(servers: BlossomServerConfig[], onUploadProgress?: BlossomUploadCallback): BlossomStore {
|
|
135
|
+
return new BlossomStore({
|
|
136
|
+
servers,
|
|
137
|
+
signer: this.signer,
|
|
138
|
+
onUploadProgress,
|
|
139
|
+
logger: (entry) => {
|
|
140
|
+
this.bandwidthTracker.apply(entry);
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
createUploadStore(onUploadProgress?: BlossomUploadCallback): BlossomStore {
|
|
146
|
+
return this.createStore(this.servers, onUploadProgress);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async upload(
|
|
150
|
+
hashHex: string,
|
|
151
|
+
data: Uint8Array,
|
|
152
|
+
_mimeType?: string,
|
|
153
|
+
onUploadProgress?: BlossomUploadCallback
|
|
154
|
+
): Promise<void> {
|
|
155
|
+
if (!this.servers.some(server => server.write)) return;
|
|
156
|
+
const uploadMimeType = 'application/octet-stream';
|
|
157
|
+
if (onUploadProgress) {
|
|
158
|
+
const store = this.createStore(this.servers, onUploadProgress);
|
|
159
|
+
await store.put(fromHex(hashHex), data, uploadMimeType);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await this.store.put(fromHex(hashHex), data, uploadMimeType);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async fetch(hashHex: string): Promise<Uint8Array | null> {
|
|
167
|
+
const inflight = this.inflightFetches.get(hashHex);
|
|
168
|
+
if (inflight) {
|
|
169
|
+
return inflight;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const pending = withReadFetchSlot(() => this.store.get(fromHex(hashHex)))
|
|
173
|
+
.finally(() => {
|
|
174
|
+
this.inflightFetches.delete(hashHex);
|
|
175
|
+
});
|
|
176
|
+
this.inflightFetches.set(hashHex, pending);
|
|
177
|
+
return await pending;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { BlossomServerConfig, ConnectivityState } from '../protocol.js';
|
|
2
|
+
|
|
3
|
+
const PROBE_TIMEOUT_MS = 3500;
|
|
4
|
+
const DUMMY_HASH = '0000000000000000000000000000000000000000000000000000000000000000';
|
|
5
|
+
|
|
6
|
+
function stripTrailingSlash(url: string): string {
|
|
7
|
+
return url.replace(/\/+$/, '');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function probe(url: string): Promise<boolean> {
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(url, {
|
|
15
|
+
method: 'HEAD',
|
|
16
|
+
// Connectivity checks should not fail just because cross-origin response
|
|
17
|
+
// headers are restricted. If the fetch resolves in no-cors mode, the
|
|
18
|
+
// endpoint is reachable.
|
|
19
|
+
mode: 'no-cors',
|
|
20
|
+
signal: controller.signal,
|
|
21
|
+
cache: 'no-store',
|
|
22
|
+
});
|
|
23
|
+
if (res.type === 'opaque') {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return res.status >= 100;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
} finally {
|
|
30
|
+
clearTimeout(timeout);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function probeConnectivity(servers: BlossomServerConfig[]): Promise<ConnectivityState> {
|
|
35
|
+
const readServers = servers.filter(server => server.read !== false);
|
|
36
|
+
const writeServers = servers.filter(server => server.write);
|
|
37
|
+
|
|
38
|
+
const [readResults, writeResults] = await Promise.all([
|
|
39
|
+
Promise.all(readServers.map(server => probe(`${stripTrailingSlash(server.url)}/${DUMMY_HASH}.bin`))),
|
|
40
|
+
Promise.all(writeServers.map(server => probe(`${stripTrailingSlash(server.url)}/upload`))),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const reachableReadServers = readResults.filter(Boolean).length;
|
|
44
|
+
const reachableWriteServers = writeResults.filter(Boolean).length;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
online: typeof navigator === 'undefined' ? true : navigator.onLine,
|
|
48
|
+
reachableReadServers,
|
|
49
|
+
totalReadServers: readServers.length,
|
|
50
|
+
reachableWriteServers,
|
|
51
|
+
totalWriteServers: writeServers.length,
|
|
52
|
+
updatedAt: Date.now(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { fromHex, sha256, toHex } from '@hashtree/core';
|
|
2
|
+
import { DexieStore } from '@hashtree/dexie';
|
|
3
|
+
|
|
4
|
+
export interface StorageStats {
|
|
5
|
+
items: number;
|
|
6
|
+
bytes: number;
|
|
7
|
+
maxBytes: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class IdbBlobStorage {
|
|
11
|
+
private readonly store: DexieStore;
|
|
12
|
+
private maxBytes: number;
|
|
13
|
+
private writesSinceEviction = 0;
|
|
14
|
+
private evictionPromise: Promise<void> | null = null;
|
|
15
|
+
|
|
16
|
+
private static readonly EVICTION_WRITE_INTERVAL = 32;
|
|
17
|
+
|
|
18
|
+
constructor(dbName: string, maxBytes: number) {
|
|
19
|
+
this.store = new DexieStore(dbName);
|
|
20
|
+
this.maxBytes = maxBytes;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setMaxBytes(maxBytes: number): void {
|
|
24
|
+
this.maxBytes = maxBytes;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getMaxBytes(): number {
|
|
28
|
+
return this.maxBytes;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async put(data: Uint8Array): Promise<string> {
|
|
32
|
+
const hashHex = toHex(await sha256(data));
|
|
33
|
+
await this.store.put(fromHex(hashHex), data);
|
|
34
|
+
void this.scheduleEviction();
|
|
35
|
+
return hashHex;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async putByHash(hashHex: string, data: Uint8Array): Promise<void> {
|
|
39
|
+
const computed = toHex(await sha256(data));
|
|
40
|
+
if (computed !== hashHex) {
|
|
41
|
+
throw new Error('Hash mismatch while caching fetched blob');
|
|
42
|
+
}
|
|
43
|
+
await this.store.put(fromHex(hashHex), data);
|
|
44
|
+
void this.scheduleEviction();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async putByHashTrusted(hashHex: string, data: Uint8Array): Promise<void> {
|
|
48
|
+
await this.store.put(fromHex(hashHex), data);
|
|
49
|
+
void this.scheduleEviction();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async get(hashHex: string): Promise<Uint8Array | null> {
|
|
53
|
+
return this.store.get(fromHex(hashHex));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async has(hashHex: string): Promise<boolean> {
|
|
57
|
+
return this.store.has(fromHex(hashHex));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async delete(hashHex: string): Promise<boolean> {
|
|
61
|
+
return this.store.delete(fromHex(hashHex));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getStats(): Promise<StorageStats> {
|
|
65
|
+
const [items, bytes] = await Promise.all([
|
|
66
|
+
this.store.count(),
|
|
67
|
+
this.store.totalBytes(),
|
|
68
|
+
]);
|
|
69
|
+
return { items, bytes, maxBytes: this.maxBytes };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
close(): void {
|
|
73
|
+
this.store.close();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private scheduleEviction(): void {
|
|
77
|
+
this.writesSinceEviction += 1;
|
|
78
|
+
if (this.writesSinceEviction < IdbBlobStorage.EVICTION_WRITE_INTERVAL) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.writesSinceEviction = 0;
|
|
82
|
+
|
|
83
|
+
if (this.evictionPromise) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.evictionPromise = this.store
|
|
88
|
+
.evict(this.maxBytes)
|
|
89
|
+
.then(() => {})
|
|
90
|
+
.finally(() => {
|
|
91
|
+
this.evictionPromise = null;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|