@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.
Files changed (258) hide show
  1. package/README.md +16 -16
  2. package/package.json +23 -19
  3. package/src/app-runtime.ts +393 -0
  4. package/src/capabilities/blossomBandwidthTracker.ts +74 -0
  5. package/src/capabilities/blossomTransport.ts +179 -0
  6. package/src/capabilities/connectivity.ts +54 -0
  7. package/src/capabilities/idbStorage.ts +94 -0
  8. package/src/capabilities/meshRouterStore.ts +426 -0
  9. package/src/capabilities/rootResolver.ts +497 -0
  10. package/src/client-id.ts +137 -0
  11. package/src/client.ts +501 -0
  12. package/{dist/entry.js → src/entry.ts} +1 -1
  13. package/src/htree-path.ts +53 -0
  14. package/src/htree-url.ts +156 -0
  15. package/src/index.ts +76 -0
  16. package/src/mediaStreaming.ts +64 -0
  17. package/src/p2p/boundedQueue.ts +168 -0
  18. package/src/p2p/errorMessage.ts +6 -0
  19. package/src/p2p/index.ts +48 -0
  20. package/src/p2p/lruCache.ts +78 -0
  21. package/src/p2p/meshQueryRouter.ts +361 -0
  22. package/src/p2p/protocol.ts +11 -0
  23. package/src/p2p/queryForwardingMachine.ts +197 -0
  24. package/src/p2p/signaling.ts +284 -0
  25. package/src/p2p/uploadRateLimiter.ts +85 -0
  26. package/src/p2p/webrtcController.ts +1168 -0
  27. package/src/p2p/webrtcProxy.ts +519 -0
  28. package/src/privacyGuards.ts +31 -0
  29. package/src/protocol.ts +124 -0
  30. package/src/relay/identity.ts +86 -0
  31. package/src/relay/mediaHandler.ts +1633 -0
  32. package/src/relay/ndk.ts +590 -0
  33. package/{dist/iris/nostr-wasm.js → src/relay/nostr-wasm.ts} +4 -1
  34. package/src/relay/nostr.ts +249 -0
  35. package/src/relay/protocol.ts +361 -0
  36. package/src/relay/publicAssetUrl.ts +25 -0
  37. package/src/relay/rootPathResolver.ts +50 -0
  38. package/src/relay/shims.d.ts +17 -0
  39. package/src/relay/signing.ts +332 -0
  40. package/src/relay/treeRootCache.ts +354 -0
  41. package/src/relay/treeRootSubscription.ts +577 -0
  42. package/src/relay/utils/constants.ts +139 -0
  43. package/src/relay/utils/errorMessage.ts +7 -0
  44. package/src/relay/utils/lruCache.ts +79 -0
  45. package/src/relay/webrtc.ts +5 -0
  46. package/src/relay/webrtcSignaling.ts +108 -0
  47. package/src/relay/worker.ts +1787 -0
  48. package/src/relay-client.ts +265 -0
  49. package/src/relay-entry.ts +1 -0
  50. package/src/runtime-network.ts +134 -0
  51. package/src/runtime.ts +153 -0
  52. package/src/transferableBytes.ts +5 -0
  53. package/src/tree-root.ts +851 -0
  54. package/src/types.ts +8 -0
  55. package/src/worker.ts +975 -0
  56. package/LICENSE +0 -21
  57. package/dist/app-runtime.d.ts +0 -60
  58. package/dist/app-runtime.d.ts.map +0 -1
  59. package/dist/app-runtime.js +0 -271
  60. package/dist/app-runtime.js.map +0 -1
  61. package/dist/capabilities/blossomBandwidthTracker.d.ts +0 -26
  62. package/dist/capabilities/blossomBandwidthTracker.d.ts.map +0 -1
  63. package/dist/capabilities/blossomBandwidthTracker.js +0 -53
  64. package/dist/capabilities/blossomBandwidthTracker.js.map +0 -1
  65. package/dist/capabilities/blossomTransport.d.ts +0 -22
  66. package/dist/capabilities/blossomTransport.d.ts.map +0 -1
  67. package/dist/capabilities/blossomTransport.js +0 -144
  68. package/dist/capabilities/blossomTransport.js.map +0 -1
  69. package/dist/capabilities/connectivity.d.ts +0 -3
  70. package/dist/capabilities/connectivity.d.ts.map +0 -1
  71. package/dist/capabilities/connectivity.js +0 -49
  72. package/dist/capabilities/connectivity.js.map +0 -1
  73. package/dist/capabilities/idbStorage.d.ts +0 -25
  74. package/dist/capabilities/idbStorage.d.ts.map +0 -1
  75. package/dist/capabilities/idbStorage.js +0 -73
  76. package/dist/capabilities/idbStorage.js.map +0 -1
  77. package/dist/capabilities/meshRouterStore.d.ts +0 -71
  78. package/dist/capabilities/meshRouterStore.d.ts.map +0 -1
  79. package/dist/capabilities/meshRouterStore.js +0 -316
  80. package/dist/capabilities/meshRouterStore.js.map +0 -1
  81. package/dist/capabilities/rootResolver.d.ts +0 -10
  82. package/dist/capabilities/rootResolver.d.ts.map +0 -1
  83. package/dist/capabilities/rootResolver.js +0 -393
  84. package/dist/capabilities/rootResolver.js.map +0 -1
  85. package/dist/client-id.d.ts +0 -18
  86. package/dist/client-id.d.ts.map +0 -1
  87. package/dist/client-id.js +0 -98
  88. package/dist/client-id.js.map +0 -1
  89. package/dist/client.d.ts +0 -61
  90. package/dist/client.d.ts.map +0 -1
  91. package/dist/client.js +0 -417
  92. package/dist/client.js.map +0 -1
  93. package/dist/entry.d.ts +0 -2
  94. package/dist/entry.d.ts.map +0 -1
  95. package/dist/entry.js.map +0 -1
  96. package/dist/htree-path.d.ts +0 -13
  97. package/dist/htree-path.d.ts.map +0 -1
  98. package/dist/htree-path.js +0 -38
  99. package/dist/htree-path.js.map +0 -1
  100. package/dist/htree-url.d.ts +0 -22
  101. package/dist/htree-url.d.ts.map +0 -1
  102. package/dist/htree-url.js +0 -118
  103. package/dist/htree-url.js.map +0 -1
  104. package/dist/index.d.ts +0 -17
  105. package/dist/index.d.ts.map +0 -1
  106. package/dist/index.js +0 -8
  107. package/dist/index.js.map +0 -1
  108. package/dist/iris/identity.d.ts +0 -36
  109. package/dist/iris/identity.d.ts.map +0 -1
  110. package/dist/iris/identity.js +0 -78
  111. package/dist/iris/identity.js.map +0 -1
  112. package/dist/iris/mediaHandler.d.ts +0 -64
  113. package/dist/iris/mediaHandler.d.ts.map +0 -1
  114. package/dist/iris/mediaHandler.js +0 -1285
  115. package/dist/iris/mediaHandler.js.map +0 -1
  116. package/dist/iris/ndk.d.ts +0 -96
  117. package/dist/iris/ndk.d.ts.map +0 -1
  118. package/dist/iris/ndk.js +0 -502
  119. package/dist/iris/ndk.js.map +0 -1
  120. package/dist/iris/nostr-wasm.d.ts +0 -14
  121. package/dist/iris/nostr-wasm.d.ts.map +0 -1
  122. package/dist/iris/nostr-wasm.js.map +0 -1
  123. package/dist/iris/nostr.d.ts +0 -60
  124. package/dist/iris/nostr.d.ts.map +0 -1
  125. package/dist/iris/nostr.js +0 -207
  126. package/dist/iris/nostr.js.map +0 -1
  127. package/dist/iris/protocol.d.ts +0 -583
  128. package/dist/iris/protocol.d.ts.map +0 -1
  129. package/dist/iris/protocol.js +0 -16
  130. package/dist/iris/protocol.js.map +0 -1
  131. package/dist/iris/publicAssetUrl.d.ts +0 -6
  132. package/dist/iris/publicAssetUrl.d.ts.map +0 -1
  133. package/dist/iris/publicAssetUrl.js +0 -14
  134. package/dist/iris/publicAssetUrl.js.map +0 -1
  135. package/dist/iris/rootPathResolver.d.ts +0 -9
  136. package/dist/iris/rootPathResolver.d.ts.map +0 -1
  137. package/dist/iris/rootPathResolver.js +0 -32
  138. package/dist/iris/rootPathResolver.js.map +0 -1
  139. package/dist/iris/signing.d.ts +0 -50
  140. package/dist/iris/signing.d.ts.map +0 -1
  141. package/dist/iris/signing.js +0 -299
  142. package/dist/iris/signing.js.map +0 -1
  143. package/dist/iris/treeRootCache.d.ts +0 -86
  144. package/dist/iris/treeRootCache.d.ts.map +0 -1
  145. package/dist/iris/treeRootCache.js +0 -269
  146. package/dist/iris/treeRootCache.js.map +0 -1
  147. package/dist/iris/treeRootSubscription.d.ts +0 -55
  148. package/dist/iris/treeRootSubscription.d.ts.map +0 -1
  149. package/dist/iris/treeRootSubscription.js +0 -479
  150. package/dist/iris/treeRootSubscription.js.map +0 -1
  151. package/dist/iris/utils/constants.d.ts +0 -76
  152. package/dist/iris/utils/constants.d.ts.map +0 -1
  153. package/dist/iris/utils/constants.js +0 -113
  154. package/dist/iris/utils/constants.js.map +0 -1
  155. package/dist/iris/utils/errorMessage.d.ts +0 -5
  156. package/dist/iris/utils/errorMessage.d.ts.map +0 -1
  157. package/dist/iris/utils/errorMessage.js +0 -8
  158. package/dist/iris/utils/errorMessage.js.map +0 -1
  159. package/dist/iris/utils/lruCache.d.ts +0 -26
  160. package/dist/iris/utils/lruCache.d.ts.map +0 -1
  161. package/dist/iris/utils/lruCache.js +0 -66
  162. package/dist/iris/utils/lruCache.js.map +0 -1
  163. package/dist/iris/webrtc.d.ts +0 -2
  164. package/dist/iris/webrtc.d.ts.map +0 -1
  165. package/dist/iris/webrtc.js +0 -3
  166. package/dist/iris/webrtc.js.map +0 -1
  167. package/dist/iris/webrtcSignaling.d.ts +0 -37
  168. package/dist/iris/webrtcSignaling.d.ts.map +0 -1
  169. package/dist/iris/webrtcSignaling.js +0 -86
  170. package/dist/iris/webrtcSignaling.js.map +0 -1
  171. package/dist/iris/worker.d.ts +0 -12
  172. package/dist/iris/worker.d.ts.map +0 -1
  173. package/dist/iris/worker.js +0 -1529
  174. package/dist/iris/worker.js.map +0 -1
  175. package/dist/iris-client.d.ts +0 -31
  176. package/dist/iris-client.d.ts.map +0 -1
  177. package/dist/iris-client.js +0 -197
  178. package/dist/iris-client.js.map +0 -1
  179. package/dist/iris-entry.d.ts +0 -2
  180. package/dist/iris-entry.d.ts.map +0 -1
  181. package/dist/iris-entry.js +0 -2
  182. package/dist/iris-entry.js.map +0 -1
  183. package/dist/mediaStreaming.d.ts +0 -7
  184. package/dist/mediaStreaming.d.ts.map +0 -1
  185. package/dist/mediaStreaming.js +0 -48
  186. package/dist/mediaStreaming.js.map +0 -1
  187. package/dist/p2p/boundedQueue.d.ts +0 -79
  188. package/dist/p2p/boundedQueue.d.ts.map +0 -1
  189. package/dist/p2p/boundedQueue.js +0 -134
  190. package/dist/p2p/boundedQueue.js.map +0 -1
  191. package/dist/p2p/errorMessage.d.ts +0 -5
  192. package/dist/p2p/errorMessage.d.ts.map +0 -1
  193. package/dist/p2p/errorMessage.js +0 -7
  194. package/dist/p2p/errorMessage.js.map +0 -1
  195. package/dist/p2p/index.d.ts +0 -8
  196. package/dist/p2p/index.d.ts.map +0 -1
  197. package/dist/p2p/index.js +0 -6
  198. package/dist/p2p/index.js.map +0 -1
  199. package/dist/p2p/lruCache.d.ts +0 -26
  200. package/dist/p2p/lruCache.d.ts.map +0 -1
  201. package/dist/p2p/lruCache.js +0 -65
  202. package/dist/p2p/lruCache.js.map +0 -1
  203. package/dist/p2p/meshQueryRouter.d.ts +0 -44
  204. package/dist/p2p/meshQueryRouter.d.ts.map +0 -1
  205. package/dist/p2p/meshQueryRouter.js +0 -228
  206. package/dist/p2p/meshQueryRouter.js.map +0 -1
  207. package/dist/p2p/protocol.d.ts +0 -10
  208. package/dist/p2p/protocol.d.ts.map +0 -1
  209. package/dist/p2p/protocol.js +0 -2
  210. package/dist/p2p/protocol.js.map +0 -1
  211. package/dist/p2p/queryForwardingMachine.d.ts +0 -46
  212. package/dist/p2p/queryForwardingMachine.d.ts.map +0 -1
  213. package/dist/p2p/queryForwardingMachine.js +0 -144
  214. package/dist/p2p/queryForwardingMachine.js.map +0 -1
  215. package/dist/p2p/signaling.d.ts +0 -63
  216. package/dist/p2p/signaling.d.ts.map +0 -1
  217. package/dist/p2p/signaling.js +0 -185
  218. package/dist/p2p/signaling.js.map +0 -1
  219. package/dist/p2p/uploadRateLimiter.d.ts +0 -21
  220. package/dist/p2p/uploadRateLimiter.d.ts.map +0 -1
  221. package/dist/p2p/uploadRateLimiter.js +0 -62
  222. package/dist/p2p/uploadRateLimiter.js.map +0 -1
  223. package/dist/p2p/webrtcController.d.ts +0 -168
  224. package/dist/p2p/webrtcController.d.ts.map +0 -1
  225. package/dist/p2p/webrtcController.js +0 -902
  226. package/dist/p2p/webrtcController.js.map +0 -1
  227. package/dist/p2p/webrtcProxy.d.ts +0 -62
  228. package/dist/p2p/webrtcProxy.d.ts.map +0 -1
  229. package/dist/p2p/webrtcProxy.js +0 -447
  230. package/dist/p2p/webrtcProxy.js.map +0 -1
  231. package/dist/privacyGuards.d.ts +0 -14
  232. package/dist/privacyGuards.d.ts.map +0 -1
  233. package/dist/privacyGuards.js +0 -27
  234. package/dist/privacyGuards.js.map +0 -1
  235. package/dist/protocol.d.ts +0 -225
  236. package/dist/protocol.d.ts.map +0 -1
  237. package/dist/protocol.js +0 -2
  238. package/dist/protocol.js.map +0 -1
  239. package/dist/runtime-network.d.ts +0 -23
  240. package/dist/runtime-network.d.ts.map +0 -1
  241. package/dist/runtime-network.js +0 -105
  242. package/dist/runtime-network.js.map +0 -1
  243. package/dist/runtime.d.ts +0 -23
  244. package/dist/runtime.d.ts.map +0 -1
  245. package/dist/runtime.js +0 -122
  246. package/dist/runtime.js.map +0 -1
  247. package/dist/tree-root.d.ts +0 -201
  248. package/dist/tree-root.d.ts.map +0 -1
  249. package/dist/tree-root.js +0 -632
  250. package/dist/tree-root.js.map +0 -1
  251. package/dist/types.d.ts +0 -2
  252. package/dist/types.d.ts.map +0 -1
  253. package/dist/types.js +0 -2
  254. package/dist/types.js.map +0 -1
  255. package/dist/worker.d.ts +0 -9
  256. package/dist/worker.d.ts.map +0 -1
  257. package/dist/worker.js +0 -797
  258. package/dist/worker.js.map +0 -1
@@ -0,0 +1,577 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Tree Root Subscription Handler
4
+ *
5
+ * Worker subscribes directly to tree root events (kind 30078 with #l=hashtree).
6
+ * Updates local cache and notifies main thread of changes.
7
+ */
8
+
9
+ import type { CID } from '@hashtree/core';
10
+ import { SimplePool, type Event as NostrEvent } from 'nostr-tools';
11
+ import { storeTreeEventSnapshot, type StoredNostrEvent } from '@hashtree/nostr';
12
+ import { getNdk, subscribe as ndkSubscribe, unsubscribe as ndkUnsubscribe } from './ndk';
13
+ import { getCachedRoot, getTreeRootCacheStore, setCachedRoot } from './treeRootCache';
14
+ import type { SignedEvent, TreeVisibility } from './protocol';
15
+ import { nip19 } from 'nostr-tools';
16
+ import { NDKSubscriptionCacheUsage } from 'ndk';
17
+
18
+ // Active subscriptions by pubkey
19
+ const activeSubscriptions = new Map<string, string>(); // pubkeyHex -> subId
20
+ const inFlightRootResolutions = new Map<string, Promise<Uint8Array | null>>();
21
+ const inFlightHistoricalRootLists = new Map<string, Promise<CID[]>>();
22
+ const historicalRootListCache = new Map<string, { roots: CID[]; expiresAt: number }>();
23
+ const MAX_HISTORICAL_TREE_ROOT_EVENTS = 20;
24
+ const HISTORICAL_TREE_ROOT_CACHE_TTL_MS = 30_000;
25
+
26
+ const DEFAULT_TREE_ROOT_RELAYS = [
27
+ 'wss://relay.damus.io',
28
+ 'wss://relay.primal.net',
29
+ 'wss://relay.nostr.band',
30
+ 'wss://relay.snort.social',
31
+ ];
32
+
33
+ // Callback to notify main thread
34
+ let notifyCallback: ((npub: string, treeName: string, record: TreeRootRecord) => void) | null = null;
35
+
36
+ export interface TreeRootRecord {
37
+ hash: Uint8Array;
38
+ key?: Uint8Array;
39
+ visibility: TreeVisibility;
40
+ labels?: string[];
41
+ updatedAt: number;
42
+ snapshotNhash?: string;
43
+ encryptedKey?: string;
44
+ keyId?: string;
45
+ selfEncryptedKey?: string;
46
+ selfEncryptedLinkKey?: string;
47
+ }
48
+
49
+ interface LegacyContentPayload {
50
+ hash?: string;
51
+ key?: string;
52
+ visibility?: TreeVisibility;
53
+ encryptedKey?: string;
54
+ keyId?: string;
55
+ selfEncryptedKey?: string;
56
+ selfEncryptedLinkKey?: string;
57
+ }
58
+
59
+ export interface ParsedTreeRootEvent {
60
+ hash: string;
61
+ key?: string;
62
+ visibility: TreeVisibility;
63
+ labels?: string[];
64
+ encryptedKey?: string;
65
+ keyId?: string;
66
+ selfEncryptedKey?: string;
67
+ selfEncryptedLinkKey?: string;
68
+ }
69
+
70
+ async function storeSnapshotNhash(event: SignedEvent): Promise<string | undefined> {
71
+ const snapshotTarget = getTreeRootCacheStore();
72
+ if (!snapshotTarget) {
73
+ return undefined;
74
+ }
75
+
76
+ const snapshot = await storeTreeEventSnapshot(
77
+ snapshotTarget,
78
+ nip19,
79
+ event as StoredNostrEvent,
80
+ );
81
+ return snapshot?.snapshotNhash;
82
+ }
83
+
84
+ function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | null> {
85
+ return Promise.race([
86
+ promise,
87
+ new Promise<null>((resolve) => setTimeout(() => resolve(null), timeoutMs)),
88
+ ]);
89
+ }
90
+
91
+ function getHistoryRelayUrls(): string[] {
92
+ const urls = new Set<string>(DEFAULT_TREE_ROOT_RELAYS);
93
+ const ndk = getNdk();
94
+ const connected = typeof ndk?.pool?.connectedRelays === 'function'
95
+ ? Array.from(ndk.pool.connectedRelays()).map((relay) => relay.url)
96
+ : [];
97
+ for (const url of connected) {
98
+ urls.add(url);
99
+ }
100
+ if (typeof ndk?.pool?.urls === 'function') {
101
+ for (const url of ndk.pool.urls()) {
102
+ urls.add(url);
103
+ }
104
+ }
105
+ return Array.from(urls);
106
+ }
107
+
108
+ function toSignedEvent(event: NostrEvent): SignedEvent {
109
+ return {
110
+ id: event.id,
111
+ pubkey: event.pubkey,
112
+ kind: event.kind,
113
+ content: event.content,
114
+ tags: event.tags,
115
+ created_at: event.created_at,
116
+ sig: event.sig,
117
+ };
118
+ }
119
+
120
+ function uniqueEvents(events: SignedEvent[]): SignedEvent[] {
121
+ const seen = new Set<string>();
122
+ const result: SignedEvent[] = [];
123
+ for (const event of events) {
124
+ const eventKey = event.id || `${event.created_at}:${event.pubkey}:${event.tags.find((tag) => tag[0] === 'd')?.[1] ?? ''}`;
125
+ if (seen.has(eventKey)) continue;
126
+ seen.add(eventKey);
127
+ result.push(event);
128
+ }
129
+ return result;
130
+ }
131
+
132
+ function compareReplaceableEvents(left: SignedEvent, right: SignedEvent): number {
133
+ const leftCreatedAt = left.created_at ?? 0;
134
+ const rightCreatedAt = right.created_at ?? 0;
135
+ if (leftCreatedAt !== rightCreatedAt) {
136
+ return rightCreatedAt - leftCreatedAt;
137
+ }
138
+
139
+ const leftId = left.id ?? '';
140
+ const rightId = right.id ?? '';
141
+ if (leftId === rightId) {
142
+ return 0;
143
+ }
144
+
145
+ if (!leftId) return 1;
146
+ if (!rightId) return -1;
147
+ return rightId.localeCompare(leftId);
148
+ }
149
+
150
+ function parseLabels(event: SignedEvent): string[] | undefined {
151
+ const seen = new Set<string>();
152
+ const labels: string[] = [];
153
+ for (const tag of event.tags) {
154
+ if (tag[0] !== 'l' || !tag[1] || seen.has(tag[1])) continue;
155
+ seen.add(tag[1]);
156
+ labels.push(tag[1]);
157
+ }
158
+ return labels.length > 0 ? labels : undefined;
159
+ }
160
+
161
+ function parseLegacyContent(event: SignedEvent): LegacyContentPayload | null {
162
+ const content = event.content?.trim();
163
+ if (!content) return null;
164
+
165
+ try {
166
+ const parsed = JSON.parse(content);
167
+ if (parsed && typeof parsed === 'object') {
168
+ const payload = parsed as Record<string, unknown>;
169
+ return {
170
+ hash: typeof payload.hash === 'string' ? payload.hash : undefined,
171
+ key: typeof payload.key === 'string' ? payload.key : undefined,
172
+ visibility: typeof payload.visibility === 'string' ? payload.visibility as TreeVisibility : undefined,
173
+ encryptedKey: typeof payload.encryptedKey === 'string' ? payload.encryptedKey : undefined,
174
+ keyId: typeof payload.keyId === 'string' ? payload.keyId : undefined,
175
+ selfEncryptedKey: typeof payload.selfEncryptedKey === 'string' ? payload.selfEncryptedKey : undefined,
176
+ selfEncryptedLinkKey: typeof payload.selfEncryptedLinkKey === 'string' ? payload.selfEncryptedLinkKey : undefined,
177
+ };
178
+ }
179
+ } catch {
180
+ // Ignore JSON parse errors.
181
+ }
182
+
183
+ if (/^[0-9a-fA-F]{64}$/.test(content)) {
184
+ return { hash: content };
185
+ }
186
+
187
+ return null;
188
+ }
189
+
190
+ export function parseTreeRootEvent(event: SignedEvent): ParsedTreeRootEvent | null {
191
+ const hashTag = event.tags.find(t => t[0] === 'hash')?.[1];
192
+ const legacyContent = hashTag ? null : parseLegacyContent(event);
193
+ const hash = hashTag ?? legacyContent?.hash;
194
+ if (!hash) return null;
195
+
196
+ const keyTag = event.tags.find(t => t[0] === 'key')?.[1];
197
+ const encryptedKeyTag = event.tags.find(t => t[0] === 'encryptedKey')?.[1];
198
+ const keyIdTag = event.tags.find(t => t[0] === 'keyId')?.[1];
199
+ const selfEncryptedKeyTag = event.tags.find(t => t[0] === 'selfEncryptedKey')?.[1];
200
+ const selfEncryptedLinkKeyTag = event.tags.find(t => t[0] === 'selfEncryptedLinkKey')?.[1];
201
+
202
+ const key = keyTag ?? legacyContent?.key;
203
+ const encryptedKey = encryptedKeyTag ?? legacyContent?.encryptedKey;
204
+ const keyId = keyIdTag ?? legacyContent?.keyId;
205
+ const selfEncryptedKey = selfEncryptedKeyTag ?? legacyContent?.selfEncryptedKey;
206
+ const selfEncryptedLinkKey = selfEncryptedLinkKeyTag ?? legacyContent?.selfEncryptedLinkKey;
207
+
208
+ let visibility: TreeVisibility;
209
+ if (encryptedKey) {
210
+ visibility = 'link-visible';
211
+ } else if (selfEncryptedKey) {
212
+ visibility = 'private';
213
+ } else {
214
+ visibility = legacyContent?.visibility ?? 'public';
215
+ }
216
+
217
+ return {
218
+ hash,
219
+ key,
220
+ visibility,
221
+ labels: parseLabels(event),
222
+ encryptedKey,
223
+ keyId,
224
+ selfEncryptedKey,
225
+ selfEncryptedLinkKey,
226
+ };
227
+ }
228
+
229
+ async function fetchTreeRootEventsFromNdk(
230
+ pubkeyHex: string,
231
+ treeName: string,
232
+ timeoutMs: number
233
+ ): Promise<SignedEvent[]> {
234
+ const ndk = getNdk();
235
+ if (!ndk) return [];
236
+
237
+ try {
238
+ const events = await withTimeout(
239
+ ndk.fetchEvents({
240
+ kinds: [30078],
241
+ authors: [pubkeyHex],
242
+ '#d': [treeName],
243
+ limit: MAX_HISTORICAL_TREE_ROOT_EVENTS,
244
+ }, {
245
+ cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY,
246
+ }),
247
+ timeoutMs,
248
+ );
249
+ if (!events) return [];
250
+ return uniqueEvents(Array.from(events).map((event) => toSignedEvent(event.rawEvent?.() ?? event)))
251
+ .sort(compareReplaceableEvents);
252
+ } catch {
253
+ return [];
254
+ }
255
+ }
256
+
257
+ async function fetchTreeRootEventsFromRelays(
258
+ pubkeyHex: string,
259
+ treeName: string,
260
+ timeoutMs: number
261
+ ): Promise<SignedEvent[]> {
262
+ const relayUrls = getHistoryRelayUrls();
263
+ if (relayUrls.length === 0) return [];
264
+
265
+ const pool = new SimplePool();
266
+ try {
267
+ const events = await withTimeout(
268
+ pool.querySync(relayUrls, {
269
+ kinds: [30078],
270
+ authors: [pubkeyHex],
271
+ '#d': [treeName],
272
+ limit: MAX_HISTORICAL_TREE_ROOT_EVENTS,
273
+ }, {
274
+ maxWait: timeoutMs,
275
+ }),
276
+ timeoutMs + 500,
277
+ );
278
+ if (!events) return [];
279
+ return uniqueEvents(Array.from(events).map((event) => toSignedEvent(event)))
280
+ .sort(compareReplaceableEvents);
281
+ } catch {
282
+ return [];
283
+ } finally {
284
+ try {
285
+ pool.close(relayUrls);
286
+ } catch {}
287
+ try {
288
+ pool.destroy();
289
+ } catch {}
290
+ }
291
+ }
292
+
293
+ function cidKey(cid: CID): string {
294
+ const hash = Array.from(cid.hash, (byte) => byte.toString(16).padStart(2, '0')).join('');
295
+ const key = cid.key
296
+ ? Array.from(cid.key, (byte) => byte.toString(16).padStart(2, '0')).join('')
297
+ : '';
298
+ return `${hash}:${key}`;
299
+ }
300
+
301
+ function dedupeRoots(roots: CID[]): CID[] {
302
+ const seen = new Set<string>();
303
+ const result: CID[] = [];
304
+ for (const root of roots) {
305
+ const key = cidKey(root);
306
+ if (seen.has(key)) continue;
307
+ seen.add(key);
308
+ result.push(root);
309
+ }
310
+ return result;
311
+ }
312
+
313
+ export async function getHistoricalTreeRoots(
314
+ npub: string,
315
+ treeName: string,
316
+ timeoutMs: number = 8000,
317
+ ): Promise<CID[]> {
318
+ const cacheKey = `${npub}/${treeName}`;
319
+ const cached = historicalRootListCache.get(cacheKey);
320
+ if (cached && cached.expiresAt > Date.now()) {
321
+ return cached.roots;
322
+ }
323
+ if (cached) {
324
+ historicalRootListCache.delete(cacheKey);
325
+ }
326
+
327
+ const inFlight = inFlightHistoricalRootLists.get(cacheKey);
328
+ if (inFlight) {
329
+ return await inFlight;
330
+ }
331
+
332
+ const lookup = (async (): Promise<CID[]> => {
333
+ let pubkeyHex: string;
334
+ try {
335
+ const decoded = nip19.decode(npub);
336
+ if (decoded.type !== 'npub') return [];
337
+ pubkeyHex = decoded.data as string;
338
+ } catch {
339
+ return [];
340
+ }
341
+
342
+ const [ndkEvents, relayEvents] = await Promise.all([
343
+ fetchTreeRootEventsFromNdk(pubkeyHex, treeName, timeoutMs),
344
+ fetchTreeRootEventsFromRelays(pubkeyHex, treeName, timeoutMs),
345
+ ]);
346
+
347
+ const roots = dedupeRoots(
348
+ uniqueEvents([...ndkEvents, ...relayEvents])
349
+ .sort(compareReplaceableEvents)
350
+ .map((event) => {
351
+ const parsed = parseTreeRootEvent(event);
352
+ if (!parsed) return null;
353
+ try {
354
+ return {
355
+ hash: hexToBytes(parsed.hash),
356
+ key: parsed.key ? hexToBytes(parsed.key) : undefined,
357
+ } as CID;
358
+ } catch {
359
+ return null;
360
+ }
361
+ })
362
+ .filter((root): root is CID => !!root),
363
+ );
364
+
365
+ historicalRootListCache.set(cacheKey, {
366
+ roots,
367
+ expiresAt: Date.now() + HISTORICAL_TREE_ROOT_CACHE_TTL_MS,
368
+ });
369
+ return roots;
370
+ })();
371
+
372
+ inFlightHistoricalRootLists.set(cacheKey, lookup);
373
+ try {
374
+ return await lookup;
375
+ } finally {
376
+ inFlightHistoricalRootLists.delete(cacheKey);
377
+ }
378
+ }
379
+
380
+ export async function resolveTreeRootNow(
381
+ npub: string,
382
+ treeName: string,
383
+ timeoutMs: number = 8000,
384
+ ): Promise<CID | null> {
385
+ const cached = await getCachedRoot(npub, treeName);
386
+ if (cached) {
387
+ return cached;
388
+ }
389
+
390
+ const cacheKey = `${npub}/${treeName}`;
391
+ const inFlight = inFlightRootResolutions.get(cacheKey);
392
+ if (inFlight) {
393
+ return await inFlight;
394
+ }
395
+
396
+ const lookup = (async (): Promise<CID | null> => {
397
+ let pubkeyHex: string;
398
+ try {
399
+ const decoded = nip19.decode(npub);
400
+ if (decoded.type !== 'npub') return null;
401
+ pubkeyHex = decoded.data as string;
402
+ } catch {
403
+ return null;
404
+ }
405
+
406
+ const fetched = (
407
+ await fetchTreeRootEventsFromNdk(pubkeyHex, treeName, timeoutMs)
408
+ )[0] ?? (
409
+ await fetchTreeRootEventsFromRelays(pubkeyHex, treeName, timeoutMs)
410
+ )[0];
411
+ if (!fetched) {
412
+ return null;
413
+ }
414
+
415
+ const parsed = parseTreeRootEvent(fetched);
416
+ if (!parsed) {
417
+ return null;
418
+ }
419
+
420
+ const hash = hexToBytes(parsed.hash);
421
+ const key = parsed.key ? hexToBytes(parsed.key) : undefined;
422
+ const { applied, record } = await setCachedRoot(npub, treeName, { hash, key }, parsed.visibility, {
423
+ updatedAt: fetched.created_at,
424
+ eventId: fetched.id,
425
+ labels: parsed.labels,
426
+ snapshotNhash: await storeSnapshotNhash(fetched),
427
+ encryptedKey: parsed.encryptedKey,
428
+ keyId: parsed.keyId,
429
+ selfEncryptedKey: parsed.selfEncryptedKey,
430
+ selfEncryptedLinkKey: parsed.selfEncryptedLinkKey,
431
+ });
432
+ historicalRootListCache.delete(cacheKey);
433
+
434
+ if (notifyCallback && applied) {
435
+ notifyCallback(npub, treeName, {
436
+ hash: record.hash,
437
+ key: record.key,
438
+ visibility: record.visibility,
439
+ labels: record.labels,
440
+ updatedAt: record.updatedAt,
441
+ snapshotNhash: record.snapshotNhash,
442
+ encryptedKey: record.encryptedKey,
443
+ keyId: record.keyId,
444
+ selfEncryptedKey: record.selfEncryptedKey,
445
+ selfEncryptedLinkKey: record.selfEncryptedLinkKey,
446
+ });
447
+ }
448
+
449
+ return { hash: record.hash, key: record.key };
450
+ })();
451
+
452
+ inFlightRootResolutions.set(cacheKey, lookup);
453
+ try {
454
+ return await lookup;
455
+ } finally {
456
+ inFlightRootResolutions.delete(cacheKey);
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Set callback to notify main thread of tree root updates
462
+ */
463
+ export function setNotifyCallback(
464
+ callback: (npub: string, treeName: string, record: TreeRootRecord) => void
465
+ ): void {
466
+ notifyCallback = callback;
467
+ }
468
+
469
+ /**
470
+ * Subscribe to tree roots for a specific pubkey
471
+ */
472
+ export function subscribeToTreeRoots(pubkeyHex: string): () => void {
473
+ // Already subscribed?
474
+ if (activeSubscriptions.has(pubkeyHex)) {
475
+ return () => unsubscribeFromTreeRoots(pubkeyHex);
476
+ }
477
+
478
+ const subId = `tree-${pubkeyHex.slice(0, 8)}`;
479
+ activeSubscriptions.set(pubkeyHex, subId);
480
+
481
+ ndkSubscribe(subId, [{
482
+ kinds: [30078],
483
+ authors: [pubkeyHex],
484
+ }], {
485
+ cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY,
486
+ });
487
+
488
+ return () => unsubscribeFromTreeRoots(pubkeyHex);
489
+ }
490
+
491
+ /**
492
+ * Unsubscribe from tree roots for a specific pubkey
493
+ */
494
+ export function unsubscribeFromTreeRoots(pubkeyHex: string): void {
495
+ const subId = activeSubscriptions.get(pubkeyHex);
496
+ if (subId) {
497
+ ndkUnsubscribe(subId);
498
+ activeSubscriptions.delete(pubkeyHex);
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Handle incoming tree root event (kind 30078 with #l=hashtree)
504
+ * Called from worker.ts event router
505
+ */
506
+ function hasLabel(event: SignedEvent, label: string): boolean {
507
+ return event.tags.some(tag => tag[0] === 'l' && tag[1] === label);
508
+ }
509
+
510
+ function hasAnyLabel(event: SignedEvent): boolean {
511
+ return event.tags.some(tag => tag[0] === 'l');
512
+ }
513
+
514
+ export async function handleTreeRootEvent(event: SignedEvent): Promise<void> {
515
+ // Extract tree name from #d tag
516
+ const dTag = event.tags.find(t => t[0] === 'd');
517
+ if (!dTag || !dTag[1]) return;
518
+ const treeName = dTag[1];
519
+
520
+ // Accept unlabeled legacy events, ignore other labeled apps.
521
+ if (hasAnyLabel(event) && !hasLabel(event, 'hashtree')) return;
522
+
523
+ const parsed = parseTreeRootEvent(event);
524
+ if (!parsed) return;
525
+
526
+ // Convert pubkey to npub
527
+ const npub = nip19.npubEncode(event.pubkey);
528
+
529
+ // Parse hash and optional key
530
+ const hash = hexToBytes(parsed.hash);
531
+ const key = parsed.key ? hexToBytes(parsed.key) : undefined;
532
+ const visibility: TreeVisibility = parsed.visibility || 'public';
533
+
534
+ // Update cache
535
+ const { applied, record } = await setCachedRoot(npub, treeName, { hash, key }, visibility, {
536
+ updatedAt: event.created_at,
537
+ eventId: event.id,
538
+ labels: parsed.labels,
539
+ snapshotNhash: await storeSnapshotNhash(event),
540
+ encryptedKey: parsed.encryptedKey,
541
+ keyId: parsed.keyId,
542
+ selfEncryptedKey: parsed.selfEncryptedKey,
543
+ selfEncryptedLinkKey: parsed.selfEncryptedLinkKey,
544
+ });
545
+ if (!applied) return;
546
+ historicalRootListCache.delete(`${npub}/${treeName}`);
547
+
548
+ // Notify main thread
549
+ if (notifyCallback) {
550
+ notifyCallback(npub, treeName, record);
551
+ }
552
+ }
553
+
554
+ /**
555
+ * Check if an event is a tree root event
556
+ */
557
+ export function isTreeRootEvent(event: SignedEvent): boolean {
558
+ if (event.kind !== 30078) return false;
559
+ if (hasLabel(event, 'hashtree')) return true;
560
+ return !hasAnyLabel(event);
561
+ }
562
+
563
+ /**
564
+ * Get all active subscription pubkeys
565
+ */
566
+ export function getActiveSubscriptions(): string[] {
567
+ return Array.from(activeSubscriptions.keys());
568
+ }
569
+
570
+ // Helper: hex string to Uint8Array
571
+ function hexToBytes(hex: string): Uint8Array {
572
+ const bytes = new Uint8Array(hex.length / 2);
573
+ for (let i = 0; i < bytes.length; i++) {
574
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
575
+ }
576
+ return bytes;
577
+ }
@@ -0,0 +1,139 @@
1
+ // @ts-nocheck
2
+ // Nostr Event Kinds Constants
3
+ // Based on NIPs (Nostr Implementation Possibilities)
4
+ // Only includes constants that are actually used in the codebase
5
+
6
+ const TEST_BOOTSTRAP_PUBKEY = import.meta.env.VITE_TEST_BOOTSTRAP_PUBKEY as string | undefined;
7
+ // Default content/social graph bootstrap pubkey (sirius)
8
+ export const DEFAULT_BOOTSTRAP_PUBKEY =
9
+ import.meta.env.VITE_TEST_MODE && TEST_BOOTSTRAP_PUBKEY
10
+ ? TEST_BOOTSTRAP_PUBKEY
11
+ : '4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
12
+
13
+ // Seed pubkeys to populate the video feed when follows are empty.
14
+ export const DEFAULT_VIDEO_FEED_PUBKEYS = [
15
+ 'feb535ca427f7ab7551adfc5fcc39132b10cbc11ce5841f05588680e94f4e71a',
16
+ ];
17
+
18
+ // NIP-01: Basic protocol
19
+ export const KIND_METADATA = 0 // User profile metadata
20
+ export const KIND_TEXT_NOTE = 1 // Text note
21
+ export const KIND_CONTACTS = 3 // Contact list (follows)
22
+
23
+ // NIP-18: Reposts
24
+ export const KIND_REPOST = 6
25
+
26
+ // NIP-25: Reactions
27
+ export const KIND_REACTION = 7
28
+
29
+ // NIP-16: Event Treatment
30
+ export const KIND_EPHEMERAL = 20000 // Ephemeral events
31
+
32
+ // NIP-04: Encrypted Direct Messages
33
+ export const KIND_CHAT_MESSAGE = 14 // Encrypted direct message (double-ratchet)
34
+
35
+ // NIP-28: Public chat
36
+ export const KIND_CHANNEL_CREATE = 40 // Channel creation
37
+ export const KIND_CHANNEL_MESSAGE = 42 // Channel message
38
+
39
+ // NIP-57: Lightning zaps
40
+ export const KIND_ZAP_RECEIPT = 9735
41
+
42
+ // NIP-51: Lists
43
+ export const KIND_MUTE_LIST = 10000 // Mute list (deprecated, use 30000)
44
+ export const KIND_FLAG_LIST = 16463 // Flagged/reported users list
45
+
46
+ // NIP-78: App-specific data
47
+ export const KIND_APP_DATA = 30078
48
+
49
+ // Long-form content
50
+ export const KIND_LONG_FORM_CONTENT = 30023
51
+
52
+ // HTTP authentication
53
+ export const KIND_HTTP_AUTH = 27235
54
+
55
+ // Blossom authorization
56
+ export const KIND_BLOSSOM_AUTH = 24242
57
+
58
+ // Debug/development
59
+ export const KIND_DEBUG_DATA = 30000 // Used for encrypted debug key-value storage
60
+
61
+ // Classified listings
62
+ export const KIND_CLASSIFIED = 30402
63
+
64
+ // Highlights
65
+ export const KIND_HIGHLIGHT = 9802
66
+
67
+ // NIP-68: Picture-first feeds
68
+ export const KIND_PICTURE_FIRST = 20
69
+
70
+ // Additional kinds found in codebase
71
+ export const KIND_WALLET_CONNECT = 6927
72
+
73
+ // NIP-34: Git Repositories
74
+ export const KIND_REPO_ANNOUNCEMENT = 30617 // Repository announcement
75
+ export const KIND_REPO_STATE = 30618 // Repository state (branch/tag tracking)
76
+ export const KIND_PATCH = 1617 // Patch (code changes under 60kb)
77
+ export const KIND_PULL_REQUEST = 1618 // Pull request
78
+ export const KIND_PR_UPDATE = 1619 // PR update (revision)
79
+ export const KIND_ISSUE = 1621 // Issue (bug report, feature request)
80
+ // Status events (1630-1633)
81
+ export const KIND_STATUS_OPEN = 1630 // Open/default
82
+ export const KIND_STATUS_APPLIED = 1631 // Applied/Merged/Resolved
83
+ export const KIND_STATUS_CLOSED = 1632 // Closed
84
+ export const KIND_STATUS_DRAFT = 1633 // Draft
85
+
86
+ // Debug namespaces for debug pkg
87
+ export const DEBUG_NAMESPACES = {
88
+ // NDK
89
+ NDK_RELAY: "ndk:relay",
90
+ NDK_RELAY_CONN: "ndk:relay:conn",
91
+ NDK_RELAY_ERROR: "ndk:relay:error",
92
+ NDK_RELAY_WARN: "ndk:relay:warn",
93
+ NDK_SUBSCRIPTION: "ndk:subscription",
94
+ NDK_SUBSCRIPTION_ERROR: "ndk:subscription:error",
95
+ NDK_SUBSCRIPTION_WARN: "ndk:subscription:warn",
96
+ NDK_CACHE: "ndk:cache",
97
+ NDK_CACHE_ERROR: "ndk:cache:error",
98
+ NDK_CACHE_WARN: "ndk:cache:warn",
99
+ NDK_POOL: "ndk:pool",
100
+ NDK_POOL_ERROR: "ndk:pool:error",
101
+ NDK_POOL_WARN: "ndk:pool:warn",
102
+ NDK_WORKER: "ndk:worker",
103
+ NDK_WORKER_ERROR: "ndk:worker:error",
104
+ NDK_WORKER_WARN: "ndk:worker:warn",
105
+ NDK_TRANSPORT: "ndk:transport",
106
+
107
+ // WebRTC
108
+ WEBRTC_PEER: "webrtc:peer",
109
+ WEBRTC_PEER_LIFECYCLE: "webrtc:peer:lifecycle", // connect/disconnect/state changes
110
+ WEBRTC_PEER_MESSAGES: "webrtc:peer:messages", // offer/answer/ICE candidates
111
+ WEBRTC_PEER_DATA: "webrtc:peer:data", // data channel events
112
+ WEBRTC_SIGNALING: "webrtc:signaling",
113
+
114
+ // Cashu
115
+ CASHU_WALLET: "cashu:wallet",
116
+ CASHU_WALLET_ERROR: "cashu:wallet:error",
117
+ CASHU_WALLET_WARN: "cashu:wallet:warn",
118
+ CASHU_MINT: "cashu:mint",
119
+ CASHU_MINT_ERROR: "cashu:mint:error",
120
+ CASHU_MINT_WARN: "cashu:mint:warn",
121
+
122
+ // UI
123
+ UI_FEED: "ui:feed",
124
+ UI_FEED_ERROR: "ui:feed:error",
125
+ UI_FEED_WARN: "ui:feed:warn",
126
+ UI_CHAT: "ui:chat",
127
+ UI_CHAT_ERROR: "ui:chat:error",
128
+ UI_CHAT_WARN: "ui:chat:warn",
129
+
130
+ // Hooks
131
+ HOOKS: "hooks",
132
+ HOOKS_ERROR: "hooks:error",
133
+ HOOKS_WARN: "hooks:warn",
134
+
135
+ // Utils
136
+ UTILS: "utils",
137
+ UTILS_ERROR: "utils:error",
138
+ UTILS_WARN: "utils:warn",
139
+ } as const