@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.
Files changed (259) hide show
  1. package/package.json +7 -3
  2. package/src/app-runtime.ts +393 -0
  3. package/src/capabilities/blossomBandwidthTracker.ts +74 -0
  4. package/src/capabilities/blossomTransport.ts +179 -0
  5. package/src/capabilities/connectivity.ts +54 -0
  6. package/src/capabilities/idbStorage.ts +94 -0
  7. package/src/capabilities/meshRouterStore.ts +426 -0
  8. package/src/capabilities/rootResolver.ts +497 -0
  9. package/src/client-id.ts +137 -0
  10. package/src/client.ts +501 -0
  11. package/{dist/entry.js → src/entry.ts} +1 -1
  12. package/src/htree-path.ts +53 -0
  13. package/src/htree-url.ts +156 -0
  14. package/src/index.ts +76 -0
  15. package/src/mediaStreaming.ts +64 -0
  16. package/src/p2p/boundedQueue.ts +168 -0
  17. package/src/p2p/errorMessage.ts +6 -0
  18. package/src/p2p/index.ts +48 -0
  19. package/src/p2p/lruCache.ts +78 -0
  20. package/src/p2p/meshQueryRouter.ts +361 -0
  21. package/src/p2p/protocol.ts +11 -0
  22. package/src/p2p/queryForwardingMachine.ts +197 -0
  23. package/src/p2p/signaling.ts +284 -0
  24. package/src/p2p/uploadRateLimiter.ts +85 -0
  25. package/src/p2p/webrtcController.ts +1168 -0
  26. package/src/p2p/webrtcProxy.ts +519 -0
  27. package/src/privacyGuards.ts +31 -0
  28. package/src/protocol.ts +124 -0
  29. package/src/relay/identity.ts +86 -0
  30. package/src/relay/mediaHandler.ts +1633 -0
  31. package/src/relay/ndk.ts +590 -0
  32. package/{dist/relay/nostr-wasm.js → src/relay/nostr-wasm.ts} +4 -1
  33. package/src/relay/nostr.ts +249 -0
  34. package/src/relay/protocol.ts +361 -0
  35. package/src/relay/publicAssetUrl.ts +25 -0
  36. package/src/relay/rootPathResolver.ts +50 -0
  37. package/src/relay/shims.d.ts +17 -0
  38. package/src/relay/signing.ts +332 -0
  39. package/src/relay/treeRootCache.ts +354 -0
  40. package/src/relay/treeRootSubscription.ts +577 -0
  41. package/src/relay/utils/constants.ts +139 -0
  42. package/src/relay/utils/errorMessage.ts +7 -0
  43. package/src/relay/utils/lruCache.ts +79 -0
  44. package/src/relay/webrtc.ts +5 -0
  45. package/src/relay/webrtcSignaling.ts +108 -0
  46. package/src/relay/worker.ts +1787 -0
  47. package/src/relay-client.ts +265 -0
  48. package/src/relay-entry.ts +1 -0
  49. package/src/runtime-network.ts +134 -0
  50. package/src/runtime.ts +153 -0
  51. package/{dist/transferableBytes.js → src/transferableBytes.ts} +2 -3
  52. package/src/tree-root.ts +851 -0
  53. package/src/types.ts +8 -0
  54. package/src/worker.ts +975 -0
  55. package/dist/app-runtime.d.ts +0 -60
  56. package/dist/app-runtime.d.ts.map +0 -1
  57. package/dist/app-runtime.js +0 -271
  58. package/dist/app-runtime.js.map +0 -1
  59. package/dist/capabilities/blossomBandwidthTracker.d.ts +0 -26
  60. package/dist/capabilities/blossomBandwidthTracker.d.ts.map +0 -1
  61. package/dist/capabilities/blossomBandwidthTracker.js +0 -53
  62. package/dist/capabilities/blossomBandwidthTracker.js.map +0 -1
  63. package/dist/capabilities/blossomTransport.d.ts +0 -22
  64. package/dist/capabilities/blossomTransport.d.ts.map +0 -1
  65. package/dist/capabilities/blossomTransport.js +0 -139
  66. package/dist/capabilities/blossomTransport.js.map +0 -1
  67. package/dist/capabilities/connectivity.d.ts +0 -3
  68. package/dist/capabilities/connectivity.d.ts.map +0 -1
  69. package/dist/capabilities/connectivity.js +0 -49
  70. package/dist/capabilities/connectivity.js.map +0 -1
  71. package/dist/capabilities/idbStorage.d.ts +0 -25
  72. package/dist/capabilities/idbStorage.d.ts.map +0 -1
  73. package/dist/capabilities/idbStorage.js +0 -73
  74. package/dist/capabilities/idbStorage.js.map +0 -1
  75. package/dist/capabilities/meshRouterStore.d.ts +0 -71
  76. package/dist/capabilities/meshRouterStore.d.ts.map +0 -1
  77. package/dist/capabilities/meshRouterStore.js +0 -316
  78. package/dist/capabilities/meshRouterStore.js.map +0 -1
  79. package/dist/capabilities/rootResolver.d.ts +0 -10
  80. package/dist/capabilities/rootResolver.d.ts.map +0 -1
  81. package/dist/capabilities/rootResolver.js +0 -392
  82. package/dist/capabilities/rootResolver.js.map +0 -1
  83. package/dist/client-id.d.ts +0 -18
  84. package/dist/client-id.d.ts.map +0 -1
  85. package/dist/client-id.js +0 -98
  86. package/dist/client-id.js.map +0 -1
  87. package/dist/client.d.ts +0 -61
  88. package/dist/client.d.ts.map +0 -1
  89. package/dist/client.js +0 -417
  90. package/dist/client.js.map +0 -1
  91. package/dist/entry.d.ts +0 -2
  92. package/dist/entry.d.ts.map +0 -1
  93. package/dist/entry.js.map +0 -1
  94. package/dist/htree-path.d.ts +0 -13
  95. package/dist/htree-path.d.ts.map +0 -1
  96. package/dist/htree-path.js +0 -38
  97. package/dist/htree-path.js.map +0 -1
  98. package/dist/htree-url.d.ts +0 -22
  99. package/dist/htree-url.d.ts.map +0 -1
  100. package/dist/htree-url.js +0 -118
  101. package/dist/htree-url.js.map +0 -1
  102. package/dist/index.d.ts +0 -17
  103. package/dist/index.d.ts.map +0 -1
  104. package/dist/index.js +0 -8
  105. package/dist/index.js.map +0 -1
  106. package/dist/mediaStreaming.d.ts +0 -7
  107. package/dist/mediaStreaming.d.ts.map +0 -1
  108. package/dist/mediaStreaming.js +0 -48
  109. package/dist/mediaStreaming.js.map +0 -1
  110. package/dist/p2p/boundedQueue.d.ts +0 -79
  111. package/dist/p2p/boundedQueue.d.ts.map +0 -1
  112. package/dist/p2p/boundedQueue.js +0 -134
  113. package/dist/p2p/boundedQueue.js.map +0 -1
  114. package/dist/p2p/errorMessage.d.ts +0 -5
  115. package/dist/p2p/errorMessage.d.ts.map +0 -1
  116. package/dist/p2p/errorMessage.js +0 -7
  117. package/dist/p2p/errorMessage.js.map +0 -1
  118. package/dist/p2p/index.d.ts +0 -8
  119. package/dist/p2p/index.d.ts.map +0 -1
  120. package/dist/p2p/index.js +0 -6
  121. package/dist/p2p/index.js.map +0 -1
  122. package/dist/p2p/lruCache.d.ts +0 -26
  123. package/dist/p2p/lruCache.d.ts.map +0 -1
  124. package/dist/p2p/lruCache.js +0 -65
  125. package/dist/p2p/lruCache.js.map +0 -1
  126. package/dist/p2p/meshQueryRouter.d.ts +0 -57
  127. package/dist/p2p/meshQueryRouter.d.ts.map +0 -1
  128. package/dist/p2p/meshQueryRouter.js +0 -264
  129. package/dist/p2p/meshQueryRouter.js.map +0 -1
  130. package/dist/p2p/protocol.d.ts +0 -10
  131. package/dist/p2p/protocol.d.ts.map +0 -1
  132. package/dist/p2p/protocol.js +0 -2
  133. package/dist/p2p/protocol.js.map +0 -1
  134. package/dist/p2p/queryForwardingMachine.d.ts +0 -46
  135. package/dist/p2p/queryForwardingMachine.d.ts.map +0 -1
  136. package/dist/p2p/queryForwardingMachine.js +0 -144
  137. package/dist/p2p/queryForwardingMachine.js.map +0 -1
  138. package/dist/p2p/signaling.d.ts +0 -63
  139. package/dist/p2p/signaling.d.ts.map +0 -1
  140. package/dist/p2p/signaling.js +0 -185
  141. package/dist/p2p/signaling.js.map +0 -1
  142. package/dist/p2p/uploadRateLimiter.d.ts +0 -21
  143. package/dist/p2p/uploadRateLimiter.d.ts.map +0 -1
  144. package/dist/p2p/uploadRateLimiter.js +0 -62
  145. package/dist/p2p/uploadRateLimiter.js.map +0 -1
  146. package/dist/p2p/webrtcController.d.ts +0 -176
  147. package/dist/p2p/webrtcController.d.ts.map +0 -1
  148. package/dist/p2p/webrtcController.js +0 -938
  149. package/dist/p2p/webrtcController.js.map +0 -1
  150. package/dist/p2p/webrtcProxy.d.ts +0 -62
  151. package/dist/p2p/webrtcProxy.d.ts.map +0 -1
  152. package/dist/p2p/webrtcProxy.js +0 -447
  153. package/dist/p2p/webrtcProxy.js.map +0 -1
  154. package/dist/privacyGuards.d.ts +0 -14
  155. package/dist/privacyGuards.d.ts.map +0 -1
  156. package/dist/privacyGuards.js +0 -27
  157. package/dist/privacyGuards.js.map +0 -1
  158. package/dist/protocol.d.ts +0 -225
  159. package/dist/protocol.d.ts.map +0 -1
  160. package/dist/protocol.js +0 -2
  161. package/dist/protocol.js.map +0 -1
  162. package/dist/relay/identity.d.ts +0 -36
  163. package/dist/relay/identity.d.ts.map +0 -1
  164. package/dist/relay/identity.js +0 -78
  165. package/dist/relay/identity.js.map +0 -1
  166. package/dist/relay/mediaHandler.d.ts +0 -64
  167. package/dist/relay/mediaHandler.d.ts.map +0 -1
  168. package/dist/relay/mediaHandler.js +0 -1285
  169. package/dist/relay/mediaHandler.js.map +0 -1
  170. package/dist/relay/ndk.d.ts +0 -96
  171. package/dist/relay/ndk.d.ts.map +0 -1
  172. package/dist/relay/ndk.js +0 -502
  173. package/dist/relay/ndk.js.map +0 -1
  174. package/dist/relay/nostr-wasm.d.ts +0 -14
  175. package/dist/relay/nostr-wasm.d.ts.map +0 -1
  176. package/dist/relay/nostr-wasm.js.map +0 -1
  177. package/dist/relay/nostr.d.ts +0 -60
  178. package/dist/relay/nostr.d.ts.map +0 -1
  179. package/dist/relay/nostr.js +0 -207
  180. package/dist/relay/nostr.js.map +0 -1
  181. package/dist/relay/protocol.d.ts +0 -592
  182. package/dist/relay/protocol.d.ts.map +0 -1
  183. package/dist/relay/protocol.js +0 -16
  184. package/dist/relay/protocol.js.map +0 -1
  185. package/dist/relay/publicAssetUrl.d.ts +0 -6
  186. package/dist/relay/publicAssetUrl.d.ts.map +0 -1
  187. package/dist/relay/publicAssetUrl.js +0 -14
  188. package/dist/relay/publicAssetUrl.js.map +0 -1
  189. package/dist/relay/rootPathResolver.d.ts +0 -9
  190. package/dist/relay/rootPathResolver.d.ts.map +0 -1
  191. package/dist/relay/rootPathResolver.js +0 -32
  192. package/dist/relay/rootPathResolver.js.map +0 -1
  193. package/dist/relay/signing.d.ts +0 -50
  194. package/dist/relay/signing.d.ts.map +0 -1
  195. package/dist/relay/signing.js +0 -299
  196. package/dist/relay/signing.js.map +0 -1
  197. package/dist/relay/treeRootCache.d.ts +0 -86
  198. package/dist/relay/treeRootCache.d.ts.map +0 -1
  199. package/dist/relay/treeRootCache.js +0 -269
  200. package/dist/relay/treeRootCache.js.map +0 -1
  201. package/dist/relay/treeRootSubscription.d.ts +0 -55
  202. package/dist/relay/treeRootSubscription.d.ts.map +0 -1
  203. package/dist/relay/treeRootSubscription.js +0 -478
  204. package/dist/relay/treeRootSubscription.js.map +0 -1
  205. package/dist/relay/utils/constants.d.ts +0 -76
  206. package/dist/relay/utils/constants.d.ts.map +0 -1
  207. package/dist/relay/utils/constants.js +0 -113
  208. package/dist/relay/utils/constants.js.map +0 -1
  209. package/dist/relay/utils/errorMessage.d.ts +0 -5
  210. package/dist/relay/utils/errorMessage.d.ts.map +0 -1
  211. package/dist/relay/utils/errorMessage.js +0 -8
  212. package/dist/relay/utils/errorMessage.js.map +0 -1
  213. package/dist/relay/utils/lruCache.d.ts +0 -26
  214. package/dist/relay/utils/lruCache.d.ts.map +0 -1
  215. package/dist/relay/utils/lruCache.js +0 -66
  216. package/dist/relay/utils/lruCache.js.map +0 -1
  217. package/dist/relay/webrtc.d.ts +0 -2
  218. package/dist/relay/webrtc.d.ts.map +0 -1
  219. package/dist/relay/webrtc.js +0 -3
  220. package/dist/relay/webrtc.js.map +0 -1
  221. package/dist/relay/webrtcSignaling.d.ts +0 -37
  222. package/dist/relay/webrtcSignaling.d.ts.map +0 -1
  223. package/dist/relay/webrtcSignaling.js +0 -86
  224. package/dist/relay/webrtcSignaling.js.map +0 -1
  225. package/dist/relay/worker.d.ts +0 -12
  226. package/dist/relay/worker.d.ts.map +0 -1
  227. package/dist/relay/worker.js +0 -1540
  228. package/dist/relay/worker.js.map +0 -1
  229. package/dist/relay-client.d.ts +0 -31
  230. package/dist/relay-client.d.ts.map +0 -1
  231. package/dist/relay-client.js +0 -197
  232. package/dist/relay-client.js.map +0 -1
  233. package/dist/relay-entry.d.ts +0 -2
  234. package/dist/relay-entry.d.ts.map +0 -1
  235. package/dist/relay-entry.js +0 -2
  236. package/dist/relay-entry.js.map +0 -1
  237. package/dist/runtime-network.d.ts +0 -23
  238. package/dist/runtime-network.d.ts.map +0 -1
  239. package/dist/runtime-network.js +0 -105
  240. package/dist/runtime-network.js.map +0 -1
  241. package/dist/runtime.d.ts +0 -24
  242. package/dist/runtime.d.ts.map +0 -1
  243. package/dist/runtime.js +0 -126
  244. package/dist/runtime.js.map +0 -1
  245. package/dist/transferableBytes.d.ts +0 -2
  246. package/dist/transferableBytes.d.ts.map +0 -1
  247. package/dist/transferableBytes.js.map +0 -1
  248. package/dist/tree-root.d.ts +0 -201
  249. package/dist/tree-root.d.ts.map +0 -1
  250. package/dist/tree-root.js +0 -632
  251. package/dist/tree-root.js.map +0 -1
  252. package/dist/types.d.ts +0 -2
  253. package/dist/types.d.ts.map +0 -1
  254. package/dist/types.js +0 -2
  255. package/dist/types.js.map +0 -1
  256. package/dist/worker.d.ts +0 -9
  257. package/dist/worker.d.ts.map +0 -1
  258. package/dist/worker.js +0 -792
  259. package/dist/worker.js.map +0 -1
@@ -0,0 +1,265 @@
1
+ import type { WorkerFactory } from './client.js';
2
+ import type {
3
+ TreeRootInfo,
4
+ WorkerConfig as RelayWorkerConfig,
5
+ WorkerRequest as RelayWorkerRequest,
6
+ WorkerResponse as RelayWorkerResponse,
7
+ } from './relay/protocol.js';
8
+
9
+ const REQUEST_TIMEOUT_MS = 30_000;
10
+
11
+ type PendingRequest = {
12
+ resolve: (message: RelayWorkerResponse) => void;
13
+ reject: (error: Error) => void;
14
+ timeoutId: ReturnType<typeof setTimeout>;
15
+ };
16
+
17
+ type RelayWorkerRequestPayload = RelayWorkerRequest extends infer T
18
+ ? T extends { id: string }
19
+ ? Omit<T, 'id'>
20
+ : never
21
+ : never;
22
+
23
+ export interface TreeRootUpdate extends TreeRootInfo {
24
+ npub: string;
25
+ treeName: string;
26
+ }
27
+
28
+ export type {
29
+ TreeRootInfo,
30
+ RelayWorkerConfig,
31
+ RelayWorkerRequest,
32
+ RelayWorkerResponse,
33
+ };
34
+
35
+ export class RelayWorkerClient {
36
+ private readonly workerFactory: WorkerFactory;
37
+ private readonly config: RelayWorkerConfig;
38
+ private worker: Worker | null = null;
39
+ private initPromise: Promise<void> | null = null;
40
+ private initPending:
41
+ | {
42
+ resolve: () => void;
43
+ reject: (error: Error) => void;
44
+ timeoutId: ReturnType<typeof setTimeout>;
45
+ }
46
+ | null = null;
47
+ private pendingRequests = new Map<string, PendingRequest>();
48
+ private treeRootListeners = new Set<(update: TreeRootUpdate) => void>();
49
+
50
+ constructor(workerFactory: WorkerFactory, config: RelayWorkerConfig) {
51
+ this.workerFactory = workerFactory;
52
+ this.config = config;
53
+ }
54
+
55
+ async init(): Promise<void> {
56
+ if (this.initPromise) return this.initPromise;
57
+
58
+ try {
59
+ this.spawnWorker();
60
+ } catch (err) {
61
+ throw err instanceof Error ? err : new Error(String(err));
62
+ }
63
+
64
+ this.initPromise = new Promise<void>((resolve, reject) => {
65
+ if (!this.worker) {
66
+ reject(new Error('Failed to create worker'));
67
+ return;
68
+ }
69
+
70
+ const timeoutId = setTimeout(() => {
71
+ this.initPending = null;
72
+ this.initPromise = null;
73
+ reject(new Error('Worker init timed out'));
74
+ }, REQUEST_TIMEOUT_MS);
75
+
76
+ this.initPending = {
77
+ resolve,
78
+ reject,
79
+ timeoutId,
80
+ };
81
+
82
+ this.worker.postMessage({
83
+ type: 'init',
84
+ id: this.nextRequestId('worker_init'),
85
+ config: this.config,
86
+ } as RelayWorkerRequest);
87
+ });
88
+
89
+ return this.initPromise;
90
+ }
91
+
92
+ private spawnWorker(): void {
93
+ if (this.workerFactory instanceof URL) {
94
+ this.worker = new Worker(this.workerFactory, { type: 'module' });
95
+ } else if (typeof this.workerFactory === 'string') {
96
+ this.worker = new Worker(this.workerFactory, { type: 'module' });
97
+ } else {
98
+ this.worker = new this.workerFactory();
99
+ }
100
+
101
+ this.worker.onmessage = (event: MessageEvent<RelayWorkerResponse>) => {
102
+ const message = event.data;
103
+
104
+ if (message.type === 'ready') {
105
+ if (this.initPending) {
106
+ clearTimeout(this.initPending.timeoutId);
107
+ this.initPending.resolve();
108
+ this.initPending = null;
109
+ }
110
+ return;
111
+ }
112
+
113
+ if (message.type === 'treeRootUpdate') {
114
+ for (const listener of this.treeRootListeners) {
115
+ const { type: _type, ...update } = message;
116
+ listener(update);
117
+ }
118
+ return;
119
+ }
120
+
121
+ if (message.type === 'error' && message.id) {
122
+ const errorMessage = typeof message.error === 'string' ? message.error : 'Worker error';
123
+ this.rejectPending(message.id, new Error(errorMessage));
124
+ return;
125
+ }
126
+
127
+ if ('id' in message && typeof message.id === 'string') {
128
+ this.resolvePending(message.id, message);
129
+ }
130
+ };
131
+
132
+ this.worker.onerror = (event) => {
133
+ const errorMessage = event instanceof ErrorEvent ? event.message : 'Worker error';
134
+ this.rejectAllPending(new Error(errorMessage));
135
+ };
136
+ }
137
+
138
+ private nextRequestId(prefix: string): string {
139
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
140
+ return `${prefix}_${crypto.randomUUID()}`;
141
+ }
142
+ return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
143
+ }
144
+
145
+ private resolvePending(id: string, message: RelayWorkerResponse): void {
146
+ const pending = this.pendingRequests.get(id);
147
+ if (!pending) return;
148
+ clearTimeout(pending.timeoutId);
149
+ pending.resolve(message);
150
+ this.pendingRequests.delete(id);
151
+ }
152
+
153
+ private rejectPending(id: string, error: Error): void {
154
+ const pending = this.pendingRequests.get(id);
155
+ if (!pending) return;
156
+ clearTimeout(pending.timeoutId);
157
+ pending.reject(error);
158
+ this.pendingRequests.delete(id);
159
+ }
160
+
161
+ private rejectAllPending(error: Error): void {
162
+ for (const [id, pending] of this.pendingRequests.entries()) {
163
+ clearTimeout(pending.timeoutId);
164
+ pending.reject(error);
165
+ this.pendingRequests.delete(id);
166
+ }
167
+
168
+ if (this.initPending) {
169
+ clearTimeout(this.initPending.timeoutId);
170
+ this.initPending.reject(error);
171
+ this.initPending = null;
172
+ }
173
+
174
+ this.initPromise = null;
175
+ }
176
+
177
+ private async request(
178
+ payload: RelayWorkerRequestPayload,
179
+ timeoutMs = REQUEST_TIMEOUT_MS,
180
+ transfer: Transferable[] = [],
181
+ ): Promise<RelayWorkerResponse> {
182
+ await this.init();
183
+ if (!this.worker) {
184
+ throw new Error('Worker not initialized');
185
+ }
186
+
187
+ const id = this.nextRequestId(payload.type);
188
+ const message = { ...payload, id } as RelayWorkerRequest;
189
+
190
+ return new Promise<RelayWorkerResponse>((resolve, reject) => {
191
+ const timeoutId = setTimeout(() => {
192
+ this.pendingRequests.delete(id);
193
+ reject(new Error(`Worker request timed out: ${payload.type}`));
194
+ }, timeoutMs);
195
+
196
+ this.pendingRequests.set(id, { resolve, reject, timeoutId });
197
+ this.worker?.postMessage(message, transfer);
198
+ });
199
+ }
200
+
201
+ async registerMediaPort(port: MessagePort, debug?: boolean): Promise<void> {
202
+ await this.init();
203
+ if (!this.worker) {
204
+ throw new Error('Worker not initialized');
205
+ }
206
+
207
+ this.worker.postMessage({ type: 'registerMediaPort', port, debug } as RelayWorkerRequest, [port]);
208
+ }
209
+
210
+ async getTreeRootInfo(npub: string, treeName: string): Promise<TreeRootInfo | null> {
211
+ const res = await this.request({ type: 'getTreeRootInfo', npub, treeName });
212
+ if (res.type !== 'treeRootInfo') {
213
+ throw new Error('Unexpected tree root response');
214
+ }
215
+ if (res.error) {
216
+ throw new Error(res.error);
217
+ }
218
+ return res.record ?? null;
219
+ }
220
+
221
+ async subscribeTreeRoots(pubkey: string): Promise<void> {
222
+ const res = await this.request({ type: 'subscribeTreeRoots', pubkey });
223
+ if (res.type !== 'void') {
224
+ throw new Error('Unexpected tree root subscribe response');
225
+ }
226
+ if (res.error) {
227
+ throw new Error(res.error);
228
+ }
229
+ }
230
+
231
+ async unsubscribeTreeRoots(pubkey: string): Promise<void> {
232
+ const res = await this.request({ type: 'unsubscribeTreeRoots', pubkey });
233
+ if (res.type !== 'void') {
234
+ throw new Error('Unexpected tree root unsubscribe response');
235
+ }
236
+ if (res.error) {
237
+ throw new Error(res.error);
238
+ }
239
+ }
240
+
241
+ onTreeRootUpdate(listener: (update: TreeRootUpdate) => void): () => void {
242
+ this.treeRootListeners.add(listener);
243
+ return () => {
244
+ this.treeRootListeners.delete(listener);
245
+ };
246
+ }
247
+
248
+ async close(): Promise<void> {
249
+ try {
250
+ const res = await this.request({ type: 'close' });
251
+ if (res.type !== 'void' && res.type !== 'error') {
252
+ throw new Error('Unexpected response for close');
253
+ }
254
+ } catch {
255
+ // Ignore close errors and always terminate locally.
256
+ }
257
+
258
+ this.treeRootListeners.clear();
259
+ this.worker?.terminate();
260
+ this.worker = null;
261
+ this.initPromise = null;
262
+ this.initPending = null;
263
+ this.rejectAllPending(new Error('Worker closed'));
264
+ }
265
+ }
@@ -0,0 +1 @@
1
+ import './relay/worker.js';
@@ -0,0 +1,134 @@
1
+ import type { BlossomServerConfig } from './protocol.js';
2
+ import { getInjectedHtreeServerUrl, type HtreeRuntimeWindowLike } from './runtime.js';
3
+
4
+ export interface RuntimeNetworkOptions {
5
+ windowLike?: HtreeRuntimeWindowLike;
6
+ }
7
+
8
+ export interface ResolveRuntimeEndpointsOptions extends RuntimeNetworkOptions {
9
+ relays?: readonly string[];
10
+ blossomServers?: readonly BlossomServerConfig[];
11
+ }
12
+
13
+ export interface RuntimeEndpoints {
14
+ htreeServerUrl: string | null;
15
+ nostrRelays: string[];
16
+ blossomServers: BlossomServerConfig[];
17
+ }
18
+
19
+ export function normalizeRuntimeServerUrl(url: string): string {
20
+ return url.trim().replace(/\/+$/, '');
21
+ }
22
+
23
+ export function normalizeRuntimeRelayUrl(url: string): string {
24
+ return url.trim().replace(/\/+$/, '');
25
+ }
26
+
27
+ function normalizeBlossomServer(server: BlossomServerConfig): BlossomServerConfig | null {
28
+ const url = normalizeRuntimeServerUrl(server.url);
29
+ if (!url) return null;
30
+ return {
31
+ url,
32
+ read: server.read ?? true,
33
+ write: server.write ?? false,
34
+ };
35
+ }
36
+
37
+ function uniqueRelayUrls(relays: readonly string[]): string[] {
38
+ const seen = new Set<string>();
39
+ const normalized: string[] = [];
40
+ for (const relay of relays) {
41
+ const url = normalizeRuntimeRelayUrl(relay);
42
+ if (!url || seen.has(url)) continue;
43
+ seen.add(url);
44
+ normalized.push(url);
45
+ }
46
+ return normalized;
47
+ }
48
+
49
+ export function getRuntimeHtreeServerUrl(windowLike?: HtreeRuntimeWindowLike): string | null {
50
+ const serverUrl = getInjectedHtreeServerUrl(windowLike);
51
+ if (!serverUrl) return null;
52
+ return normalizeRuntimeServerUrl(serverUrl);
53
+ }
54
+
55
+ export function getRuntimeNostrRelayUrl(windowLike?: HtreeRuntimeWindowLike): string | null {
56
+ const serverUrl = getRuntimeHtreeServerUrl(windowLike);
57
+ if (!serverUrl) return null;
58
+
59
+ try {
60
+ const url = new URL(serverUrl);
61
+ if (url.protocol === 'http:') {
62
+ url.protocol = 'ws:';
63
+ } else if (url.protocol === 'https:') {
64
+ url.protocol = 'wss:';
65
+ } else {
66
+ return null;
67
+ }
68
+ url.pathname = '/ws';
69
+ url.search = '';
70
+ url.hash = '';
71
+ return normalizeRuntimeRelayUrl(url.toString());
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ export function getRuntimeBlossomServer(windowLike?: HtreeRuntimeWindowLike): BlossomServerConfig | null {
78
+ const serverUrl = getRuntimeHtreeServerUrl(windowLike);
79
+ if (!serverUrl) return null;
80
+ return {
81
+ url: serverUrl,
82
+ read: true,
83
+ write: true,
84
+ };
85
+ }
86
+
87
+ export function getRuntimeNostrRelays(
88
+ relays: readonly string[],
89
+ options: RuntimeNetworkOptions = {},
90
+ ): string[] {
91
+ const runtimeRelayUrl = getRuntimeNostrRelayUrl(options.windowLike);
92
+ if (runtimeRelayUrl) {
93
+ return [runtimeRelayUrl];
94
+ }
95
+ return uniqueRelayUrls(relays);
96
+ }
97
+
98
+ export function getRuntimeBlossomServers(
99
+ servers: readonly BlossomServerConfig[],
100
+ options: RuntimeNetworkOptions = {},
101
+ ): BlossomServerConfig[] {
102
+ const merged = new Map<string, BlossomServerConfig>();
103
+ const runtimeServer = getRuntimeBlossomServer(options.windowLike);
104
+ const candidates = runtimeServer ? [runtimeServer, ...servers] : [...servers];
105
+
106
+ for (const candidate of candidates) {
107
+ const normalized = normalizeBlossomServer(candidate);
108
+ if (!normalized) continue;
109
+
110
+ const existing = merged.get(normalized.url);
111
+ if (existing) {
112
+ merged.set(normalized.url, {
113
+ url: normalized.url,
114
+ read: existing.read || normalized.read,
115
+ write: existing.write || normalized.write,
116
+ });
117
+ continue;
118
+ }
119
+
120
+ merged.set(normalized.url, normalized);
121
+ }
122
+
123
+ return Array.from(merged.values());
124
+ }
125
+
126
+ export function resolveRuntimeEndpoints(
127
+ options: ResolveRuntimeEndpointsOptions = {},
128
+ ): RuntimeEndpoints {
129
+ return {
130
+ htreeServerUrl: getRuntimeHtreeServerUrl(options.windowLike),
131
+ nostrRelays: getRuntimeNostrRelays(options.relays ?? [], options),
132
+ blossomServers: getRuntimeBlossomServers(options.blossomServers ?? [], options),
133
+ };
134
+ }
package/src/runtime.ts ADDED
@@ -0,0 +1,153 @@
1
+ export interface HtreeRuntimeLocationLike {
2
+ protocol?: string;
3
+ hostname?: string;
4
+ search?: string;
5
+ }
6
+
7
+ export interface HtreeRuntimeWindowLike {
8
+ location?: HtreeRuntimeLocationLike;
9
+ __HTREE_SERVER_URL__?: string;
10
+ __HTREE_CANONICAL_URL__?: string | null;
11
+ htree?: {
12
+ htreeBaseUrl?: string;
13
+ };
14
+ }
15
+
16
+ export interface ResolveRuntimeHtreeBaseUrlOptions {
17
+ windowLike?: HtreeRuntimeWindowLike;
18
+ fallbackBaseUrl?: string | null;
19
+ }
20
+
21
+ function getWindowLike(windowLike?: HtreeRuntimeWindowLike): HtreeRuntimeWindowLike | null {
22
+ if (windowLike) return windowLike;
23
+ if (typeof window === 'undefined') return null;
24
+ return window as HtreeRuntimeWindowLike;
25
+ }
26
+
27
+ function normalizeBaseUrl(url: string | null | undefined): string {
28
+ return (url ?? '').trim().replace(/\/$/, '');
29
+ }
30
+
31
+ function getQueryParam(name: string, windowLike?: HtreeRuntimeWindowLike): string | null {
32
+ const runtimeWindow = getWindowLike(windowLike);
33
+ if (!runtimeWindow) return null;
34
+ try {
35
+ const value = new URLSearchParams(runtimeWindow.location?.search ?? '').get(name);
36
+ return typeof value === 'string' ? value.trim() || null : null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ function getPageProtocol(windowLike?: HtreeRuntimeWindowLike): string | null {
43
+ const runtimeWindow = getWindowLike(windowLike);
44
+ const protocol = runtimeWindow?.location?.protocol;
45
+ return typeof protocol === 'string' ? protocol.toLowerCase() : null;
46
+ }
47
+
48
+ function getPageHostname(windowLike?: HtreeRuntimeWindowLike): string | null {
49
+ const runtimeWindow = getWindowLike(windowLike);
50
+ const hostname = runtimeWindow?.location?.hostname;
51
+ return typeof hostname === 'string' ? hostname.toLowerCase() : null;
52
+ }
53
+
54
+ function hasCanonicalHtreeIdentity(windowLike?: HtreeRuntimeWindowLike): boolean {
55
+ const runtimeWindow = getWindowLike(windowLike);
56
+ const injectedCanonical = runtimeWindow?.__HTREE_CANONICAL_URL__;
57
+ const canonical = typeof injectedCanonical === 'string' && injectedCanonical.trim()
58
+ ? injectedCanonical.trim()
59
+ : getQueryParam('htree_canonical', windowLike);
60
+ return typeof canonical === 'string' && canonical.toLowerCase().startsWith('htree://');
61
+ }
62
+
63
+ function isLoopbackChildRuntime(windowLike?: HtreeRuntimeWindowLike): boolean {
64
+ if (getPageProtocol(windowLike) !== 'http:') return false;
65
+ const hostname = getPageHostname(windowLike);
66
+ if (!hostname) return false;
67
+ return hostname === '127.0.0.1'
68
+ || hostname === 'localhost'
69
+ || hostname.endsWith('.htree.localhost');
70
+ }
71
+
72
+ function getServerProtocol(serverUrl: string): string | null {
73
+ try {
74
+ return new URL(serverUrl).protocol.toLowerCase();
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ function isLocalHttpServerUrl(serverUrl: string): boolean {
81
+ try {
82
+ const parsed = new URL(serverUrl);
83
+ const hostname = parsed.hostname.toLowerCase();
84
+ return (parsed.protocol === 'http:' || parsed.protocol === 'https:')
85
+ && (hostname === '127.0.0.1' || hostname === 'localhost' || hostname.endsWith('.htree.localhost'));
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ function getWindowHtreeBaseUrl(windowLike?: HtreeRuntimeWindowLike): string {
92
+ const runtimeWindow = getWindowLike(windowLike);
93
+ return normalizeBaseUrl(runtimeWindow?.htree?.htreeBaseUrl);
94
+ }
95
+
96
+ export function getInjectedHtreeServerUrl(windowLike?: HtreeRuntimeWindowLike): string | null {
97
+ const runtimeWindow = getWindowLike(windowLike);
98
+ if (!runtimeWindow) return null;
99
+ const override = runtimeWindow.__HTREE_SERVER_URL__;
100
+ const fallback = getQueryParam('htree_server', runtimeWindow);
101
+ const candidate = typeof override === 'string' && override.trim() ? override : fallback;
102
+ const normalized = normalizeBaseUrl(candidate);
103
+ return normalized || null;
104
+ }
105
+
106
+ export function shouldEagerLoadMediaInNativeChildRuntime(windowLike?: HtreeRuntimeWindowLike): boolean {
107
+ return isLoopbackChildRuntime(windowLike) && hasCanonicalHtreeIdentity(windowLike);
108
+ }
109
+
110
+ export function shouldPreferSameOriginHtreeRoutes(windowLike?: HtreeRuntimeWindowLike): boolean {
111
+ const serverUrl = getInjectedHtreeServerUrl(windowLike);
112
+ if (!serverUrl) return false;
113
+ const serverProtocol = getServerProtocol(serverUrl);
114
+ if (serverProtocol !== 'http:') return false;
115
+
116
+ const pageProtocol = getPageProtocol(windowLike);
117
+ if (pageProtocol === 'https:') return true;
118
+ if (pageProtocol === 'htree:') {
119
+ const hostname = getPageHostname(windowLike);
120
+ return hostname?.startsWith('npub1') === true || hostname === 'self';
121
+ }
122
+ if (hasCanonicalHtreeIdentity(windowLike) && !isLoopbackChildRuntime(windowLike)) return true;
123
+ return false;
124
+ }
125
+
126
+ export function canUseInjectedHtreeServerUrl(windowLike?: HtreeRuntimeWindowLike): boolean {
127
+ const serverUrl = getInjectedHtreeServerUrl(windowLike);
128
+ return !!serverUrl && !shouldPreferSameOriginHtreeRoutes(windowLike);
129
+ }
130
+
131
+ export function canUseSameOriginHtreeProtocolStreaming(windowLike?: HtreeRuntimeWindowLike): boolean {
132
+ return getPageProtocol(windowLike) === 'htree:';
133
+ }
134
+
135
+ export function resolveRuntimeHtreeBaseUrl(
136
+ options: ResolveRuntimeHtreeBaseUrlOptions = {},
137
+ ): string {
138
+ const { windowLike, fallbackBaseUrl } = options;
139
+ const injectedServerUrl = getInjectedHtreeServerUrl(windowLike);
140
+
141
+ if (injectedServerUrl && canUseInjectedHtreeServerUrl(windowLike)) {
142
+ return injectedServerUrl;
143
+ }
144
+
145
+ const windowBaseUrl = getWindowHtreeBaseUrl(windowLike);
146
+ if (windowBaseUrl) {
147
+ if (!isLocalHttpServerUrl(windowBaseUrl) || canUseInjectedHtreeServerUrl(windowLike)) {
148
+ return windowBaseUrl;
149
+ }
150
+ }
151
+
152
+ return normalizeBaseUrl(fallbackBaseUrl);
153
+ }
@@ -1,6 +1,5 @@
1
1
  // Worker replies may reuse bytes from caches or stores. Copy them before
2
2
  // transfer so postMessage ownership changes do not detach the source buffer.
3
- export function cloneTransferableBytes(bytes) {
4
- return bytes.slice();
3
+ export function cloneTransferableBytes(bytes: Uint8Array): Uint8Array {
4
+ return bytes.slice();
5
5
  }
6
- //# sourceMappingURL=transferableBytes.js.map