@hashtree/worker
v0.2.16
Published
Modular browser worker for hashtree blob caching, tree-root state, and Blossom connectivity
Maintainers
Readme
@hashtree/worker
Modular browser worker for hashtree blob caching and Blossom connectivity.
Runs hashtree storage operations in a Web Worker to keep the main thread free. Handles IndexedDB caching, Blossom server uploads/downloads, connectivity probing, and WebRTC P2P data exchange.
Install
npm install @hashtree/workerUsage
import { HashtreeWorkerClient } from '@hashtree/worker';
import HashtreeWorker from './workers/hashtree.worker.ts?worker';
const client = new HashtreeWorkerClient(HashtreeWorker, {
blossomServers: [{ url: 'https://upload.example', read: true, write: true }],
});
await client.init();
// Store and retrieve blobs
const { hashHex } = await client.putBlob(data);
const { data: blob } = await client.getBlob(hashHex);Plain Worker + App-Owned Nostr
For simple apps, keep ordinary Nostr reads in app code and let the worker focus on storage, Blossom, and mesh transport:
import { HashtreeWorkerClient } from '@hashtree/worker';
import { ManagedWebRTCMeshHost } from '@hashtree/worker/p2p';
import { SimplePool } from 'nostr-tools/pool';
const client = new HashtreeWorkerClient(HashtreeWorker, {
storeName: 'demo-worker',
});
await client.init();
const meshHost = new ManagedWebRTCMeshHost();
meshHost.attachWorkerClient(client);
const pool = new SimplePool();
const profileSub = pool.subscribeMany(
['wss://relay.damus.io'],
[{ kinds: [0], authors: ['pubkey-hex'] }],
{
onevent(event) {
console.log('profile metadata', event.content);
},
},
);That split keeps HashtreeWorkerClient reusable for apps that only need blob/media
transport, while still allowing each app to use nostr-tools or another thin client
for profiles, follows, search, or other product-specific Nostr queries.
Relay Worker Client
If you are using @hashtree/worker/relay-entry?worker and need relay-backed tree-root
metadata or subscription calls, use the dedicated relay wrapper:
import { RelayWorkerClient } from '@hashtree/worker/relay-client';
import HashtreeWorker from '@hashtree/worker/relay-entry?worker';
const client = new RelayWorkerClient(HashtreeWorker, {
storeName: 'demo-sites-worker',
relays: ['wss://relay.damus.io'],
blossomServers: [{ url: 'https://upload.example', read: false, write: true }],
pubkey: '336f319763657d6b0e65a5b5876719e8c8dcdcf9396852be71ee26b73368b29b',
});
await client.init();
const root = await client.getTreeRootInfo('npub1example', 'sites/example');
const stop = client.onTreeRootUpdate((update) => {
console.log(update.treeName, update.visibility);
});Browser Runtime Defaults
When the app runs inside Iris or another shell that injects window.__HTREE_SERVER_URL__
(or the launch URL carries htree_server), the main app-facing API should be
createHtreeRuntime(...):
import {
HashtreeWorkerClient,
createHtreeRuntime,
} from '@hashtree/worker';
import HashtreeWorker from './workers/hashtree.worker.ts?worker';
const DEFAULT_RELAYS = [
'wss://relay.damus.io',
'wss://relay.primal.net',
];
const DEFAULT_BLOSSOM_SERVERS = [
{ url: 'https://upload.example', read: false, write: true },
{ url: 'https://cdn.example', read: true, write: false },
];
const runtime = createHtreeRuntime({
appId: 'my-app',
relays: DEFAULT_RELAYS,
blossomServers: DEFAULT_BLOSSOM_SERVERS,
});
const workerClient = new HashtreeWorkerClient(HashtreeWorker, {
...runtime.getWorkerConfig({
storeName: 'my-app-worker',
}),
});
const mediaUrl = runtime.urls.media('htree://nhash1example/video.mp4', {
clientScoped: true,
mimeType: 'video/mp4',
});
await runtime.media.ensureReady({
registerMediaPort: (port) => workerClient.registerMediaPort(port),
});Embedding In A Custom Worker
If you already have your own worker and do not want @hashtree/worker to own the whole
worker entrypoint, attach the protocol handler yourself:
import { attachHashtreeWorker } from '@hashtree/worker/worker';
attachHashtreeWorker(self);
self.addEventListener('message', (event) => {
if (event.data?.type === 'my-custom-message') {
// Your own worker logic.
}
});If you want stricter isolation, attach it to a dedicated MessagePort instead of self.
That lets a larger worker multiplex hashtree traffic without sharing one global message
channel.
Behavior:
- In plain web mode,
runtime.endpointskeeps your configured public relays and Blossom servers. - In native child runtimes,
runtime.endpointsandruntime.getWorkerConfig()switch transport defaults to the local daemon endpoints. runtime.urls.media(...)handles/htree/...URL generation plus the per-clienthtree_cand optionalhtree_tquery params.runtime.media.ensureReady(...)handles the common page-side service-worker/media-port handshake.
Transport Notes
- If you expect media or files to keep working through the worker, daemon, or WebRTC peers, app-facing URLs should stay in
htree://...or/htree/...form. Rawhttps://Blossom URLs bypass the runtime routing and will fail when a client intentionally has no direct Blossom access. - Mesh reads should be treated as liveness-based, not fixed-timeout-based. Slow peers are normal on cold paths; callers should hedge to more peers instead of converting a slow in-flight read into a synthetic not-found.
- After bytes or fragments start flowing, prefer idle/progress-based expiry over total wall-clock deadlines. That keeps large media transfers alive without giving malicious peers an unlimited pin.
- For realistic verification, test cold direct navigation with requester-side Blossom disabled and assert that artwork/audio loads from
/htree/...without any fallback requests to default Blossom servers.
Service Worker Client Keys
If your service worker intercepts /htree/... media requests and forwards them to a worker over MessagePort, use a stable per-tab client key:
createHtreeRuntime(...)generates it once per tab/webview.runtime.urls.media(..., { clientScoped: true })appends it as thehtree_cquery param.runtime.media.ensureReady(...)sends the same key inREGISTER_WORKER_PORTandPING_WORKER_PORT.
That lets the service worker map fetches back to the correct worker port when multiple tabs or isolated child webviews are active at once, without falling back to a single global port.
Exports
@hashtree/worker—createHtreeRuntime,resolveRuntimeEndpoints, andHashtreeWorkerClient@hashtree/worker/relay-client—RelayWorkerClientplus relay-backed tree-root metadata types@hashtree/worker/worker—attachHashtreeWorker(...)for embedding the worker protocol into a custom worker@hashtree/worker/p2p—WebRTCController/WebRTCProxyfor P2P data channel management@hashtree/worker/entry— Worker entry point@hashtree/worker/protocol— Shared message types between main thread and worker
License
MIT
