holosphere 2.0.0-alpha11 → 2.0.0-alpha13

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 (65) hide show
  1. package/dist/{2019-D2OG2idw.js → 2019-CLMqIAfQ.js} +1722 -1668
  2. package/dist/{2019-D2OG2idw.js.map → 2019-CLMqIAfQ.js.map} +1 -1
  3. package/dist/2019-Cp3uYhyY.cjs +8 -0
  4. package/dist/{2019-EION3wKo.cjs.map → 2019-Cp3uYhyY.cjs.map} +1 -1
  5. package/dist/browser-D6cNVl0v.cjs +2 -0
  6. package/dist/{browser-Cq59Ij19.cjs.map → browser-D6cNVl0v.cjs.map} +1 -1
  7. package/dist/{browser-BSniCNqO.js → browser-nUQt1cnB.js} +2 -2
  8. package/dist/{browser-BSniCNqO.js.map → browser-nUQt1cnB.js.map} +1 -1
  9. package/dist/cjs/holosphere.cjs +1 -1
  10. package/dist/esm/holosphere.js +67 -50
  11. package/dist/{index-D-jZhliX.js → index-BN_uoxQK.js} +20324 -735
  12. package/dist/index-BN_uoxQK.js.map +1 -0
  13. package/dist/{index-Bl6rM1NW.js → index-CoAjtqsD.js} +2 -2
  14. package/dist/{index-Bl6rM1NW.js.map → index-CoAjtqsD.js.map} +1 -1
  15. package/dist/{index-Bwg3OzRM.cjs → index-Cp3tI53z.cjs} +3 -3
  16. package/dist/{index-Bwg3OzRM.cjs.map → index-Cp3tI53z.cjs.map} +1 -1
  17. package/dist/index-DJjGSwXG.cjs +13 -0
  18. package/dist/index-DJjGSwXG.cjs.map +1 -0
  19. package/dist/index-V8EHMYEY.cjs +29 -0
  20. package/dist/index-V8EHMYEY.cjs.map +1 -0
  21. package/dist/index-Z5TstN1e.js +11663 -0
  22. package/dist/index-Z5TstN1e.js.map +1 -0
  23. package/dist/indexeddb-storage-CZK5A7XH.cjs +2 -0
  24. package/dist/indexeddb-storage-CZK5A7XH.cjs.map +1 -0
  25. package/dist/{indexeddb-storage-5eiUNsHC.js → indexeddb-storage-bpA01pAU.js} +39 -2
  26. package/dist/indexeddb-storage-bpA01pAU.js.map +1 -0
  27. package/dist/{memory-storage-DMt36uZO.cjs → memory-storage-B1k8Jszd.cjs} +2 -2
  28. package/dist/{memory-storage-DMt36uZO.cjs.map → memory-storage-B1k8Jszd.cjs.map} +1 -1
  29. package/dist/{memory-storage-CI-gfmuG.js → memory-storage-BqhmytP_.js} +2 -2
  30. package/dist/{memory-storage-CI-gfmuG.js.map → memory-storage-BqhmytP_.js.map} +1 -1
  31. package/docs/FEDERATION.md +474 -0
  32. package/package.json +3 -1
  33. package/src/crypto/nostr-utils.js +7 -0
  34. package/src/crypto/secp256k1.js +104 -38
  35. package/src/federation/capabilities.js +162 -0
  36. package/src/federation/card-storage.js +376 -0
  37. package/src/federation/handshake.js +561 -9
  38. package/src/federation/hologram.js +194 -57
  39. package/src/federation/holon-registry.js +187 -0
  40. package/src/federation/index.js +68 -0
  41. package/src/federation/registry.js +164 -6
  42. package/src/federation/request-card.js +373 -0
  43. package/src/hierarchical/upcast.js +19 -3
  44. package/src/index.js +209 -75
  45. package/src/lib/federation-methods.js +527 -5
  46. package/src/storage/indexeddb-storage.js +41 -0
  47. package/src/storage/nostr-async.js +14 -5
  48. package/src/storage/nostr-client.js +471 -155
  49. package/src/storage/nostr-wrapper.js +6 -3
  50. package/dist/2019-EION3wKo.cjs +0 -8
  51. package/dist/_commonjsHelpers-C37NGDzP.cjs +0 -2
  52. package/dist/_commonjsHelpers-C37NGDzP.cjs.map +0 -1
  53. package/dist/_commonjsHelpers-CUmg6egw.js +0 -7
  54. package/dist/_commonjsHelpers-CUmg6egw.js.map +0 -1
  55. package/dist/browser-Cq59Ij19.cjs +0 -2
  56. package/dist/index-D-jZhliX.js.map +0 -1
  57. package/dist/index-Dc6Z8Aob.cjs +0 -18
  58. package/dist/index-Dc6Z8Aob.cjs.map +0 -1
  59. package/dist/indexeddb-storage-5eiUNsHC.js.map +0 -1
  60. package/dist/indexeddb-storage-FNFUVvTJ.cjs +0 -2
  61. package/dist/indexeddb-storage-FNFUVvTJ.cjs.map +0 -1
  62. package/dist/secp256k1-CEwJNcfV.js +0 -1890
  63. package/dist/secp256k1-CEwJNcfV.js.map +0 -1
  64. package/dist/secp256k1-CiEONUnj.cjs +0 -12
  65. package/dist/secp256k1-CiEONUnj.cjs.map +0 -1
@@ -0,0 +1,376 @@
1
+ /**
2
+ * @fileoverview Federation Card Storage
3
+ *
4
+ * Persists federation card state to prevent cards from reappearing
5
+ * after deletion. Manages card lifecycle (pending, sent, accepted,
6
+ * rejected, dismissed).
7
+ *
8
+ * @module federation/card-storage
9
+ */
10
+
11
+ import { writeGlobal, readGlobal, getAllGlobal } from '../storage/global-tables.js';
12
+ import { serializeCard, deserializeCard, shouldDisplayCard } from './request-card.js';
13
+
14
+ const FEDERATION_CARDS_TABLE = '_federation-cards';
15
+ const DISMISSED_REQUESTS_TABLE = '_dismissed-federation-requests';
16
+ const PROCESSED_RESPONSES_TABLE = '_processed-federation-responses';
17
+ const DM_STATE_TABLE = '_federation-dm-state';
18
+ const PROCESSED_DMS_TABLE = '_processed-federation-dms';
19
+
20
+ /**
21
+ * Save a federation card
22
+ * @param {Object} client - NostrClient instance
23
+ * @param {string} appname - Application namespace
24
+ * @param {Object} card - Federation card configuration
25
+ * @returns {Promise<boolean>} Success indicator
26
+ */
27
+ export async function saveCard(client, appname, card) {
28
+ const serialized = serializeCard(card);
29
+ serialized.id = card.id;
30
+ return writeGlobal(client, appname, FEDERATION_CARDS_TABLE, serialized);
31
+ }
32
+
33
+ /**
34
+ * Get a specific federation card
35
+ * @param {Object} client - NostrClient instance
36
+ * @param {string} appname - Application namespace
37
+ * @param {string} cardId - Card ID
38
+ * @returns {Promise<Object|null>} Card or null
39
+ */
40
+ export async function getCard(client, appname, cardId) {
41
+ const data = await readGlobal(client, appname, FEDERATION_CARDS_TABLE, cardId);
42
+ return data ? deserializeCard(data) : null;
43
+ }
44
+
45
+ /**
46
+ * Get all federation cards
47
+ * @param {Object} client - NostrClient instance
48
+ * @param {string} appname - Application namespace
49
+ * @returns {Promise<Object[]>} Array of cards
50
+ */
51
+ export async function getAllCards(client, appname) {
52
+ const data = await getAllGlobal(client, appname, FEDERATION_CARDS_TABLE);
53
+ return (data || [])
54
+ .filter(d => d && !d._deleted)
55
+ .map(d => deserializeCard(d));
56
+ }
57
+
58
+ /**
59
+ * Get cards that should be displayed
60
+ * @param {Object} client - NostrClient instance
61
+ * @param {string} appname - Application namespace
62
+ * @returns {Promise<Object[]>} Array of displayable cards
63
+ */
64
+ export async function getDisplayableCards(client, appname) {
65
+ const cards = await getAllCards(client, appname);
66
+ return cards.filter(shouldDisplayCard);
67
+ }
68
+
69
+ /**
70
+ * Get card by partner public key
71
+ * @param {Object} client - NostrClient instance
72
+ * @param {string} appname - Application namespace
73
+ * @param {string} partnerPubKey - Partner's public key
74
+ * @returns {Promise<Object|null>} Card or null
75
+ */
76
+ export async function getCardByPartner(client, appname, partnerPubKey) {
77
+ const cards = await getAllCards(client, appname);
78
+ return cards.find(c => c.partnerPubKey === partnerPubKey) || null;
79
+ }
80
+
81
+ /**
82
+ * Delete a federation card
83
+ * @param {Object} client - NostrClient instance
84
+ * @param {string} appname - Application namespace
85
+ * @param {string} cardId - Card ID
86
+ * @returns {Promise<boolean>} Success indicator
87
+ */
88
+ export async function deleteCard(client, appname, cardId) {
89
+ const card = await getCard(client, appname, cardId);
90
+ if (!card) return false;
91
+
92
+ // Mark as deleted
93
+ const deleted = {
94
+ ...card,
95
+ id: cardId,
96
+ _deleted: true,
97
+ deletedAt: Date.now(),
98
+ };
99
+
100
+ return writeGlobal(client, appname, FEDERATION_CARDS_TABLE, deleted);
101
+ }
102
+
103
+ /**
104
+ * Dismiss a card (mark as dismissed so it won't reappear)
105
+ * @param {Object} client - NostrClient instance
106
+ * @param {string} appname - Application namespace
107
+ * @param {string} cardId - Card ID
108
+ * @returns {Promise<boolean>} Success indicator
109
+ */
110
+ export async function dismissCard(client, appname, cardId) {
111
+ const card = await getCard(client, appname, cardId);
112
+ if (!card) return false;
113
+
114
+ card.status = 'dismissed';
115
+ card.dismissedAt = Date.now();
116
+
117
+ return saveCard(client, appname, card);
118
+ }
119
+
120
+ /**
121
+ * Mark a federation request as dismissed (by request ID)
122
+ * This prevents the request from showing up again as a card
123
+ * @param {Object} client - NostrClient instance
124
+ * @param {string} appname - Application namespace
125
+ * @param {string} requestId - Request ID from federation request
126
+ * @returns {Promise<boolean>} Success indicator
127
+ */
128
+ export async function dismissRequest(client, appname, requestId) {
129
+ const entry = {
130
+ id: requestId,
131
+ dismissedAt: Date.now(),
132
+ };
133
+ return writeGlobal(client, appname, DISMISSED_REQUESTS_TABLE, entry);
134
+ }
135
+
136
+ /**
137
+ * Check if a request has been dismissed
138
+ * @param {Object} client - NostrClient instance
139
+ * @param {string} appname - Application namespace
140
+ * @param {string} requestId - Request ID
141
+ * @returns {Promise<boolean>} True if dismissed
142
+ */
143
+ export async function isRequestDismissed(client, appname, requestId) {
144
+ const entry = await readGlobal(client, appname, DISMISSED_REQUESTS_TABLE, requestId);
145
+ return !!entry;
146
+ }
147
+
148
+ /**
149
+ * Get all dismissed request IDs
150
+ * @param {Object} client - NostrClient instance
151
+ * @param {string} appname - Application namespace
152
+ * @returns {Promise<Set<string>>} Set of dismissed request IDs
153
+ */
154
+ export async function getDismissedRequestIds(client, appname) {
155
+ const entries = await getAllGlobal(client, appname, DISMISSED_REQUESTS_TABLE);
156
+ return new Set((entries || []).map(e => e.id));
157
+ }
158
+
159
+ /**
160
+ * Mark a federation response as processed
161
+ * This prevents duplicate "federation accepted" messages
162
+ * @param {Object} client - NostrClient instance
163
+ * @param {string} appname - Application namespace
164
+ * @param {string} requestId - Original request ID
165
+ * @param {string} responderPubKey - Responder's public key
166
+ * @returns {Promise<boolean>} Success indicator
167
+ */
168
+ export async function markResponseProcessed(client, appname, requestId, responderPubKey) {
169
+ const entry = {
170
+ id: `${requestId}_${responderPubKey}`,
171
+ requestId,
172
+ responderPubKey,
173
+ processedAt: Date.now(),
174
+ };
175
+ return writeGlobal(client, appname, PROCESSED_RESPONSES_TABLE, entry);
176
+ }
177
+
178
+ /**
179
+ * Check if a response has already been processed
180
+ * @param {Object} client - NostrClient instance
181
+ * @param {string} appname - Application namespace
182
+ * @param {string} requestId - Original request ID
183
+ * @param {string} responderPubKey - Responder's public key
184
+ * @returns {Promise<boolean>} True if already processed
185
+ */
186
+ export async function isResponseProcessed(client, appname, requestId, responderPubKey) {
187
+ const id = `${requestId}_${responderPubKey}`;
188
+ const entry = await readGlobal(client, appname, PROCESSED_RESPONSES_TABLE, id);
189
+ return !!entry;
190
+ }
191
+
192
+ /**
193
+ * Get all processed response IDs
194
+ * @param {Object} client - NostrClient instance
195
+ * @param {string} appname - Application namespace
196
+ * @returns {Promise<Set<string>>} Set of processed response IDs
197
+ */
198
+ export async function getProcessedResponseIds(client, appname) {
199
+ const entries = await getAllGlobal(client, appname, PROCESSED_RESPONSES_TABLE);
200
+ return new Set((entries || []).map(e => e.id));
201
+ }
202
+
203
+ /**
204
+ * Update card status
205
+ * @param {Object} client - NostrClient instance
206
+ * @param {string} appname - Application namespace
207
+ * @param {string} cardId - Card ID
208
+ * @param {string} status - New status
209
+ * @returns {Promise<boolean>} Success indicator
210
+ */
211
+ export async function updateCardStatus(client, appname, cardId, status) {
212
+ const card = await getCard(client, appname, cardId);
213
+ if (!card) return false;
214
+
215
+ card.status = status;
216
+ card.statusUpdatedAt = Date.now();
217
+
218
+ return saveCard(client, appname, card);
219
+ }
220
+
221
+ /**
222
+ * Find pending cards for a partner
223
+ * @param {Object} client - NostrClient instance
224
+ * @param {string} appname - Application namespace
225
+ * @param {string} partnerPubKey - Partner's public key
226
+ * @returns {Promise<Object[]>} Array of pending cards
227
+ */
228
+ export async function findPendingCardsForPartner(client, appname, partnerPubKey) {
229
+ const cards = await getAllCards(client, appname);
230
+ return cards.filter(c =>
231
+ c.partnerPubKey === partnerPubKey &&
232
+ c.status === 'pending'
233
+ );
234
+ }
235
+
236
+ /**
237
+ * Clean up old dismissed entries (older than maxAge)
238
+ * @param {Object} client - NostrClient instance
239
+ * @param {string} appname - Application namespace
240
+ * @param {number} maxAge - Maximum age in milliseconds (default: 30 days)
241
+ * @returns {Promise<Object>} Cleanup result
242
+ */
243
+ export async function cleanupOldEntries(client, appname, maxAge = 30 * 24 * 60 * 60 * 1000) {
244
+ const result = {
245
+ dismissedCleaned: 0,
246
+ responsesCleaned: 0,
247
+ cardsCleaned: 0,
248
+ };
249
+
250
+ const now = Date.now();
251
+ const cutoff = now - maxAge;
252
+
253
+ // Cleanup old dismissed requests
254
+ const dismissed = await getAllGlobal(client, appname, DISMISSED_REQUESTS_TABLE);
255
+ for (const entry of (dismissed || [])) {
256
+ if (entry.dismissedAt && entry.dismissedAt < cutoff) {
257
+ await writeGlobal(client, appname, DISMISSED_REQUESTS_TABLE, {
258
+ id: entry.id,
259
+ _deleted: true,
260
+ });
261
+ result.dismissedCleaned++;
262
+ }
263
+ }
264
+
265
+ // Cleanup old processed responses
266
+ const responses = await getAllGlobal(client, appname, PROCESSED_RESPONSES_TABLE);
267
+ for (const entry of (responses || [])) {
268
+ if (entry.processedAt && entry.processedAt < cutoff) {
269
+ await writeGlobal(client, appname, PROCESSED_RESPONSES_TABLE, {
270
+ id: entry.id,
271
+ _deleted: true,
272
+ });
273
+ result.responsesCleaned++;
274
+ }
275
+ }
276
+
277
+ // Cleanup old dismissed cards
278
+ const cards = await getAllCards(client, appname);
279
+ for (const card of cards) {
280
+ if (card.status === 'dismissed' && card.dismissedAt && card.dismissedAt < cutoff) {
281
+ await deleteCard(client, appname, card.id);
282
+ result.cardsCleaned++;
283
+ }
284
+ }
285
+
286
+ return result;
287
+ }
288
+
289
+ /**
290
+ * Get the last DM fetch timestamp
291
+ * @param {Object} client - NostrClient instance
292
+ * @param {string} appname - Application namespace
293
+ * @returns {Promise<number>} Unix timestamp (seconds) of last fetch, or 0 if never fetched
294
+ */
295
+ export async function getLastDMFetchTime(client, appname) {
296
+ const entry = await readGlobal(client, appname, DM_STATE_TABLE, 'lastFetch');
297
+ return entry?.timestamp || 0;
298
+ }
299
+
300
+ /**
301
+ * Set the last DM fetch timestamp
302
+ * @param {Object} client - NostrClient instance
303
+ * @param {string} appname - Application namespace
304
+ * @param {number} timestamp - Unix timestamp (seconds)
305
+ * @returns {Promise<boolean>} Success indicator
306
+ */
307
+ export async function setLastDMFetchTime(client, appname, timestamp) {
308
+ return writeGlobal(client, appname, DM_STATE_TABLE, {
309
+ id: 'lastFetch',
310
+ timestamp,
311
+ updatedAt: Date.now(),
312
+ });
313
+ }
314
+
315
+ /**
316
+ * Mark a DM as processed by its Nostr event ID
317
+ * @param {Object} client - NostrClient instance
318
+ * @param {string} appname - Application namespace
319
+ * @param {string} eventId - Nostr event ID
320
+ * @param {string} type - DM type (request/response)
321
+ * @returns {Promise<boolean>} Success indicator
322
+ */
323
+ export async function markDMProcessed(client, appname, eventId, type = 'unknown') {
324
+ return writeGlobal(client, appname, PROCESSED_DMS_TABLE, {
325
+ id: eventId,
326
+ type,
327
+ processedAt: Date.now(),
328
+ });
329
+ }
330
+
331
+ /**
332
+ * Check if a DM has been processed
333
+ * @param {Object} client - NostrClient instance
334
+ * @param {string} appname - Application namespace
335
+ * @param {string} eventId - Nostr event ID
336
+ * @returns {Promise<boolean>} True if already processed
337
+ */
338
+ export async function isDMProcessed(client, appname, eventId) {
339
+ const entry = await readGlobal(client, appname, PROCESSED_DMS_TABLE, eventId);
340
+ return !!entry;
341
+ }
342
+
343
+ /**
344
+ * Get all processed DM event IDs
345
+ * @param {Object} client - NostrClient instance
346
+ * @param {string} appname - Application namespace
347
+ * @returns {Promise<Set<string>>} Set of processed event IDs
348
+ */
349
+ export async function getProcessedDMIds(client, appname) {
350
+ const entries = await getAllGlobal(client, appname, PROCESSED_DMS_TABLE);
351
+ return new Set((entries || []).map(e => e.id));
352
+ }
353
+
354
+ export default {
355
+ saveCard,
356
+ getCard,
357
+ getAllCards,
358
+ getDisplayableCards,
359
+ getCardByPartner,
360
+ deleteCard,
361
+ dismissCard,
362
+ dismissRequest,
363
+ isRequestDismissed,
364
+ getDismissedRequestIds,
365
+ markResponseProcessed,
366
+ isResponseProcessed,
367
+ getProcessedResponseIds,
368
+ updateCardStatus,
369
+ findPendingCardsForPartner,
370
+ cleanupOldEntries,
371
+ getLastDMFetchTime,
372
+ setLastDMFetchTime,
373
+ markDMProcessed,
374
+ isDMProcessed,
375
+ getProcessedDMIds,
376
+ };