holosphere 2.0.0-alpha6 → 2.0.0-alpha8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +1 -1
- package/dist/{index-NOravBLu.js → index-4XHHKe6S.js} +383 -102
- package/dist/index-4XHHKe6S.js.map +1 -0
- package/dist/{index-JFz-dW43.js → index-BjP1TXGz.js} +2 -2
- package/dist/{index-JFz-dW43.js.map → index-BjP1TXGz.js.map} +1 -1
- package/dist/{index-CmzkI7SI.cjs → index-CKffQDmQ.cjs} +2 -2
- package/dist/{index-CmzkI7SI.cjs.map → index-CKffQDmQ.cjs.map} +1 -1
- package/dist/index-Dz5kOZMI.cjs +5 -0
- package/dist/index-Dz5kOZMI.cjs.map +1 -0
- package/dist/{indexeddb-storage-C4HsulhA.cjs → indexeddb-storage-DD7EFBVc.cjs} +2 -2
- package/dist/{indexeddb-storage-C4HsulhA.cjs.map → indexeddb-storage-DD7EFBVc.cjs.map} +1 -1
- package/dist/{indexeddb-storage-OtSAVDZY.js → indexeddb-storage-lExjjFlV.js} +2 -2
- package/dist/{indexeddb-storage-OtSAVDZY.js.map → indexeddb-storage-lExjjFlV.js.map} +1 -1
- package/dist/{memory-storage-ChpcYvxA.js → memory-storage-C68adso2.js} +2 -2
- package/dist/{memory-storage-ChpcYvxA.js.map → memory-storage-C68adso2.js.map} +1 -1
- package/dist/{memory-storage-MD6ED00P.cjs → memory-storage-DD_6yyXT.cjs} +2 -2
- package/dist/{memory-storage-MD6ED00P.cjs.map → memory-storage-DD_6yyXT.cjs.map} +1 -1
- package/dist/{secp256k1-DcTYQrqC.cjs → secp256k1-DYELiqgx.cjs} +2 -2
- package/dist/{secp256k1-DcTYQrqC.cjs.map → secp256k1-DYELiqgx.cjs.map} +1 -1
- package/dist/{secp256k1-PfNOEI7a.js → secp256k1-OM8siPyy.js} +2 -2
- package/dist/{secp256k1-PfNOEI7a.js.map → secp256k1-OM8siPyy.js.map} +1 -1
- package/examples/holosphere-widget.js +1242 -0
- package/examples/widget-demo.html +274 -0
- package/examples/widget.html +703 -0
- package/package.json +3 -1
- package/src/cdn-entry.js +22 -0
- package/src/contracts/queries.js +16 -1
- package/src/core/holosphere.js +2 -2
- package/src/crypto/nostr-utils.js +36 -2
- package/src/federation/handshake.js +16 -4
- package/src/index.js +16 -2
- package/src/storage/backends/gundb-backend.js +293 -9
- package/src/storage/gun-async.js +14 -12
- package/src/storage/gun-auth.js +26 -18
- package/src/storage/gun-wrapper.js +75 -41
- package/src/storage/nostr-async.js +40 -25
- package/src/storage/unified-storage.js +31 -1
- package/vite.config.cdn.js +60 -0
- package/dist/index-BtKHqqet.cjs +0 -5
- package/dist/index-BtKHqqet.cjs.map +0 -1
- package/dist/index-NOravBLu.js.map +0 -1
package/src/storage/gun-auth.js
CHANGED
|
@@ -274,28 +274,36 @@ export class GunAuth {
|
|
|
274
274
|
return;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
}).length;
|
|
282
|
-
|
|
283
|
-
// If no references, we're done
|
|
284
|
-
if (expectedCount === 0) {
|
|
285
|
-
settled = true;
|
|
286
|
-
resolve([]);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
277
|
+
// Pre-parse inline items (not Gun references)
|
|
278
|
+
for (const key of keys) {
|
|
279
|
+
const rawItem = parentData[key];
|
|
280
|
+
if (!rawItem) continue;
|
|
289
281
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (settled || !data || key.startsWith('_') || seen.has(key)) return;
|
|
293
|
-
seen.add(key);
|
|
282
|
+
// Skip Gun references - will be fetched via map().once()
|
|
283
|
+
if (typeof rawItem === 'object' && rawItem['#']) continue;
|
|
294
284
|
|
|
295
|
-
const parsed = parseItem(
|
|
296
|
-
if (parsed && !parsed._deleted) {
|
|
285
|
+
const parsed = parseItem(rawItem);
|
|
286
|
+
if (parsed && !parsed._deleted && !seen.has(key)) {
|
|
287
|
+
seen.add(key);
|
|
297
288
|
results.push(parsed);
|
|
298
289
|
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Expected count is ALL keys (map().once() fires for all)
|
|
293
|
+
expectedCount = keys.length;
|
|
294
|
+
|
|
295
|
+
// Step 2: Collect items via map().once(), counting as we go
|
|
296
|
+
ref.map().once((data, key) => {
|
|
297
|
+
if (settled || !data || key.startsWith('_')) return;
|
|
298
|
+
|
|
299
|
+
// Count every item, but only add if not already seen
|
|
300
|
+
if (!seen.has(key)) {
|
|
301
|
+
seen.add(key);
|
|
302
|
+
const parsed = parseItem(data);
|
|
303
|
+
if (parsed && !parsed._deleted) {
|
|
304
|
+
results.push(parsed);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
299
307
|
receivedCount++;
|
|
300
308
|
tryResolve();
|
|
301
309
|
});
|
|
@@ -54,15 +54,32 @@ function encodePathComponent(component) {
|
|
|
54
54
|
return encodeURIComponent(component).replace(/%2F/g, '/');
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Navigate to a Gun path using chained .get() calls
|
|
59
|
+
* Gun treats 'a/b/c' as a literal key, not a path.
|
|
60
|
+
* This function splits the path and chains .get() calls properly.
|
|
61
|
+
* @private
|
|
62
|
+
* @param {Object} gun - Gun instance
|
|
63
|
+
* @param {string} path - Path string like "appname/holon/lens/key"
|
|
64
|
+
* @returns {Object} Gun chain reference at the path
|
|
65
|
+
*/
|
|
66
|
+
function getGunPath(gun, path) {
|
|
67
|
+
const parts = path.split('/').filter(p => p.length > 0);
|
|
68
|
+
let ref = gun;
|
|
69
|
+
for (const part of parts) {
|
|
70
|
+
ref = ref.get(part);
|
|
71
|
+
}
|
|
72
|
+
return ref;
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
/**
|
|
58
76
|
* Serialize data for GunDB storage
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* The deserialization handles both this format and raw JSON strings for reading old data
|
|
77
|
+
* Stores data as raw JSON string for compatibility with holosphere original
|
|
78
|
+
* This matches the format used in holosphere v1 for better interoperability
|
|
62
79
|
* @private
|
|
63
80
|
*/
|
|
64
81
|
function serializeForGun(data) {
|
|
65
|
-
return
|
|
82
|
+
return JSON.stringify(data);
|
|
66
83
|
}
|
|
67
84
|
|
|
68
85
|
/**
|
|
@@ -152,11 +169,21 @@ function deserializeFromGun(data) {
|
|
|
152
169
|
export async function write(gun, path, data) {
|
|
153
170
|
try {
|
|
154
171
|
const serialized = serializeForGun(data);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
const parts = path.split('/').filter(p => p.length > 0);
|
|
173
|
+
console.log('[gun-wrapper] write:', { path, parts, dataId: data?.id });
|
|
174
|
+
const ref = getGunPath(gun, path);
|
|
175
|
+
console.log('[gun-wrapper] write ref soul:', ref?._.get);
|
|
176
|
+
const ack = await gunPut(ref, serialized, 5000); // Increased timeout from 2s to 5s
|
|
177
|
+
console.log('[gun-wrapper] write ack:', { ok: ack.ok, timeout: ack.timeout });
|
|
178
|
+
if (ack.timeout) {
|
|
179
|
+
console.warn('[gun-wrapper] write timed out (data may not be persisted):', path);
|
|
180
|
+
}
|
|
181
|
+
console.log('[gun-wrapper] write complete:', path);
|
|
182
|
+
|
|
183
|
+
// Return ack info so caller can handle timeouts
|
|
184
|
+
return { ok: true, timeout: ack.timeout || false };
|
|
159
185
|
} catch (error) {
|
|
186
|
+
console.error('[gun-wrapper] write error:', error);
|
|
160
187
|
throw error;
|
|
161
188
|
}
|
|
162
189
|
}
|
|
@@ -168,7 +195,12 @@ export async function write(gun, path, data) {
|
|
|
168
195
|
* @returns {Promise<Object|null>} Data or null if not found
|
|
169
196
|
*/
|
|
170
197
|
export async function read(gun, path) {
|
|
171
|
-
const
|
|
198
|
+
const parts = path.split('/').filter(p => p.length > 0);
|
|
199
|
+
console.log('[gun-wrapper] read:', { path, parts });
|
|
200
|
+
const ref = getGunPath(gun, path);
|
|
201
|
+
console.log('[gun-wrapper] read ref soul:', ref?._.get);
|
|
202
|
+
const rawData = await gunPromise(ref, 2000);
|
|
203
|
+
console.log('[gun-wrapper] read rawData:', rawData ? (typeof rawData === 'string' ? rawData.substring(0, 100) : 'object') : 'null');
|
|
172
204
|
|
|
173
205
|
if (!rawData) {
|
|
174
206
|
return null;
|
|
@@ -193,53 +225,52 @@ export async function read(gun, path) {
|
|
|
193
225
|
* @returns {Promise<Object[]>} Array of data objects
|
|
194
226
|
*/
|
|
195
227
|
export async function readAll(gun, path, timeout = 5000) {
|
|
228
|
+
const parts = path.split('/').filter(p => p.length > 0);
|
|
229
|
+
console.log('[gun-wrapper] readAll:', { path, parts });
|
|
230
|
+
|
|
196
231
|
return new Promise((resolve) => {
|
|
197
232
|
const output = new Map();
|
|
198
233
|
let settled = false;
|
|
199
234
|
let expectedCount = 0;
|
|
200
235
|
let receivedCount = 0;
|
|
201
236
|
|
|
202
|
-
const ref = gun
|
|
237
|
+
const ref = getGunPath(gun, path);
|
|
238
|
+
console.log('[gun-wrapper] readAll ref soul:', ref?._.get);
|
|
203
239
|
|
|
204
240
|
const tryResolve = () => {
|
|
205
241
|
if (settled) return;
|
|
206
242
|
if (expectedCount > 0 && receivedCount >= expectedCount) {
|
|
207
243
|
settled = true;
|
|
244
|
+
console.log('[gun-wrapper] readAll resolved with', output.size, 'items');
|
|
208
245
|
resolve(Array.from(output.values()));
|
|
209
246
|
}
|
|
210
247
|
};
|
|
211
248
|
|
|
212
249
|
const parseItem = (data) => {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
let item = null;
|
|
216
|
-
if (data._json && typeof data._json === 'string') {
|
|
217
|
-
try {
|
|
218
|
-
item = JSON.parse(data._json);
|
|
219
|
-
} catch (e) {}
|
|
220
|
-
} else if (typeof data === 'string') {
|
|
221
|
-
try {
|
|
222
|
-
item = JSON.parse(data);
|
|
223
|
-
} catch (e) {}
|
|
224
|
-
}
|
|
225
|
-
return item;
|
|
250
|
+
return deserializeFromGun(data);
|
|
226
251
|
};
|
|
227
252
|
|
|
228
253
|
// Step 1: Get the parent data to count expected items
|
|
229
254
|
ref.once((parentData) => {
|
|
230
255
|
if (settled) return;
|
|
256
|
+
console.log('[gun-wrapper] readAll parentData:', parentData);
|
|
257
|
+
console.log('[gun-wrapper] readAll parentData keys:', parentData ? Object.keys(parentData).filter(k => k !== '_') : 'null');
|
|
258
|
+
console.log('[gun-wrapper] readAll parentData type:', typeof parentData);
|
|
231
259
|
|
|
232
260
|
if (!parentData) {
|
|
233
261
|
settled = true;
|
|
262
|
+
console.log('[gun-wrapper] readAll: no parent data, returning empty');
|
|
234
263
|
resolve([]);
|
|
235
264
|
return;
|
|
236
265
|
}
|
|
237
266
|
|
|
238
267
|
// Get all keys except Gun metadata
|
|
239
268
|
const keys = Object.keys(parentData).filter(k => k !== '_');
|
|
269
|
+
console.log('[gun-wrapper] readAll keys:', keys);
|
|
240
270
|
|
|
241
271
|
if (keys.length === 0) {
|
|
242
272
|
settled = true;
|
|
273
|
+
console.log('[gun-wrapper] readAll: no keys, returning empty');
|
|
243
274
|
resolve([]);
|
|
244
275
|
return;
|
|
245
276
|
}
|
|
@@ -257,40 +288,38 @@ export async function readAll(gun, path, timeout = 5000) {
|
|
|
257
288
|
continue;
|
|
258
289
|
}
|
|
259
290
|
|
|
260
|
-
// Try to parse inline data
|
|
291
|
+
// Try to parse inline data (don't count yet - will count in map().once() phase)
|
|
261
292
|
const item = parseItem(rawItem);
|
|
262
293
|
if (item && item.id && !item._deleted) {
|
|
263
294
|
output.set(item.id, item);
|
|
264
|
-
receivedCount++;
|
|
265
295
|
}
|
|
266
296
|
}
|
|
267
297
|
|
|
268
|
-
// Set expected count:
|
|
269
|
-
|
|
298
|
+
// Set expected count: ALL keys that could have data (references + inline)
|
|
299
|
+
// We use total keys because map().once() will fire for all of them
|
|
300
|
+
expectedCount = keys.length;
|
|
270
301
|
|
|
271
|
-
// If no
|
|
302
|
+
// If no keys, we're done (shouldn't happen but be safe)
|
|
272
303
|
if (expectedCount === 0) {
|
|
273
304
|
settled = true;
|
|
274
305
|
resolve(Array.from(output.values()));
|
|
275
306
|
return;
|
|
276
307
|
}
|
|
277
308
|
|
|
278
|
-
// Step 2: Use map().once() to resolve
|
|
309
|
+
// Step 2: Use map().once() to resolve all items, counting as we go
|
|
279
310
|
ref.map().once((data, key) => {
|
|
280
311
|
if (settled || !data || key === '_') return;
|
|
281
312
|
|
|
282
313
|
const item = parseItem(data);
|
|
283
314
|
if (item && item.id && !item._deleted) {
|
|
315
|
+
// Add to output if not already there (inline items already added)
|
|
284
316
|
if (!output.has(item.id)) {
|
|
285
317
|
output.set(item.id, item);
|
|
286
|
-
receivedCount++;
|
|
287
|
-
tryResolve();
|
|
288
318
|
}
|
|
289
|
-
} else {
|
|
290
|
-
// Item was null/deleted, still count it as received
|
|
291
|
-
receivedCount++;
|
|
292
|
-
tryResolve();
|
|
293
319
|
}
|
|
320
|
+
// Count every item received (inline or reference)
|
|
321
|
+
receivedCount++;
|
|
322
|
+
tryResolve();
|
|
294
323
|
});
|
|
295
324
|
});
|
|
296
325
|
|
|
@@ -312,7 +341,7 @@ export async function readAll(gun, path, timeout = 5000) {
|
|
|
312
341
|
* @returns {Promise<boolean>} Success indicator
|
|
313
342
|
*/
|
|
314
343
|
export async function update(gun, path, updates) {
|
|
315
|
-
const rawData = await gunPromise(gun
|
|
344
|
+
const rawData = await gunPromise(getGunPath(gun, path));
|
|
316
345
|
|
|
317
346
|
if (!rawData) {
|
|
318
347
|
return false; // Not found
|
|
@@ -329,7 +358,9 @@ export async function update(gun, path, updates) {
|
|
|
329
358
|
|
|
330
359
|
try {
|
|
331
360
|
const serialized = serializeForGun(merged);
|
|
332
|
-
await gunPut(gun
|
|
361
|
+
await gunPut(getGunPath(gun, path), serialized, 2000);
|
|
362
|
+
// Add delay for propagation
|
|
363
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
333
364
|
return true;
|
|
334
365
|
} catch (error) {
|
|
335
366
|
throw error;
|
|
@@ -345,7 +376,7 @@ export async function update(gun, path, updates) {
|
|
|
345
376
|
export async function deleteData(gun, path) {
|
|
346
377
|
try {
|
|
347
378
|
// First read existing data to get the id
|
|
348
|
-
const rawData = await gunPromise(gun
|
|
379
|
+
const rawData = await gunPromise(getGunPath(gun, path));
|
|
349
380
|
if (!rawData) {
|
|
350
381
|
return true; // Already deleted/doesn't exist
|
|
351
382
|
}
|
|
@@ -359,7 +390,9 @@ export async function deleteData(gun, path) {
|
|
|
359
390
|
_deletedAt: Date.now()
|
|
360
391
|
};
|
|
361
392
|
|
|
362
|
-
await gunPut(gun
|
|
393
|
+
await gunPut(getGunPath(gun, path), serializeForGun(tombstone), 2000);
|
|
394
|
+
// Add delay for propagation
|
|
395
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
363
396
|
return true;
|
|
364
397
|
} catch (error) {
|
|
365
398
|
throw error;
|
|
@@ -403,7 +436,7 @@ export function subscribe(gun, path, callback, options = {}) {
|
|
|
403
436
|
|
|
404
437
|
if (isPrefix) {
|
|
405
438
|
// Subscribe to all items under this prefix
|
|
406
|
-
const ref = gun
|
|
439
|
+
const ref = getGunPath(gun, path);
|
|
407
440
|
|
|
408
441
|
ref.map().on((data, key) => {
|
|
409
442
|
if (data && !key.startsWith('_')) {
|
|
@@ -425,7 +458,7 @@ export function subscribe(gun, path, callback, options = {}) {
|
|
|
425
458
|
};
|
|
426
459
|
} else {
|
|
427
460
|
// Subscribe to single item
|
|
428
|
-
const listener = gun
|
|
461
|
+
const listener = getGunPath(gun, path).on((data, key) => {
|
|
429
462
|
if (data) {
|
|
430
463
|
const deserialized = deserializeFromGun(data);
|
|
431
464
|
if (deserialized && !deserialized._deleted) {
|
|
@@ -464,6 +497,7 @@ export async function writeGlobal(gun, appname, tableName, data) {
|
|
|
464
497
|
throw new Error('writeGlobal: data must have an id field');
|
|
465
498
|
}
|
|
466
499
|
const path = buildGlobalPath(appname, tableName, data.id);
|
|
500
|
+
// Use write function which includes the propagation delay
|
|
467
501
|
return write(gun, path, data);
|
|
468
502
|
}
|
|
469
503
|
|
|
@@ -65,28 +65,33 @@ export async function nostrGet(client, path, kind = 30000, options = {}) {
|
|
|
65
65
|
if (!options.skipPersistent && client.persistentGet) {
|
|
66
66
|
const persistedEvent = await client.persistentGet(path);
|
|
67
67
|
if (persistedEvent && persistedEvent.content) {
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// Verify author is in requested authors list (persistent storage may have cached events from other authors)
|
|
69
|
+
if (!authors.includes(persistedEvent.pubkey)) {
|
|
70
|
+
// Author mismatch - fall through to relay query
|
|
71
|
+
} else {
|
|
72
|
+
try {
|
|
73
|
+
const data = JSON.parse(persistedEvent.content);
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
// Skip deleted items
|
|
76
|
+
if (data._deleted) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
// Optionally include author information
|
|
81
|
+
if (options.includeAuthor) {
|
|
82
|
+
data._author = persistedEvent.pubkey;
|
|
83
|
+
}
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
// Trigger background refresh from relays (fire-and-forget)
|
|
86
|
+
if (client.refreshPathInBackground) {
|
|
87
|
+
client.refreshPathInBackground(path, kind, { authors, timeout });
|
|
88
|
+
}
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
return data;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
// Fall through to relay query if parsing fails
|
|
93
|
+
console.warn('[nostrGet] Failed to parse persisted event:', error);
|
|
94
|
+
}
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
97
|
}
|
|
@@ -136,12 +141,15 @@ async function _executeNostrGet(client, path, kind, authors, timeout, options) {
|
|
|
136
141
|
|
|
137
142
|
const events = await client.query(filter, { timeout });
|
|
138
143
|
|
|
139
|
-
|
|
144
|
+
// Filter by author (relays may not respect authors filter)
|
|
145
|
+
const authoredEvents = events.filter(event => authors.includes(event.pubkey));
|
|
146
|
+
|
|
147
|
+
if (authoredEvents.length === 0) {
|
|
140
148
|
return null;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
// Get most recent event (across
|
|
144
|
-
const event =
|
|
151
|
+
// Get most recent event (across allowed authors)
|
|
152
|
+
const event = authoredEvents.sort((a, b) => b.created_at - a.created_at)[0];
|
|
145
153
|
|
|
146
154
|
try {
|
|
147
155
|
const data = JSON.parse(event.content);
|
|
@@ -187,6 +195,9 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
|
|
|
187
195
|
for (const event of persistedEvents) {
|
|
188
196
|
if (!event || !event.tags) continue;
|
|
189
197
|
|
|
198
|
+
// Verify author is in requested authors list (persistent storage may have cached events from other authors)
|
|
199
|
+
if (!authors.includes(event.pubkey)) continue;
|
|
200
|
+
|
|
190
201
|
const dTag = event.tags.find(t => t[0] === 'd');
|
|
191
202
|
if (!dTag || !dTag[1] || !dTag[1].startsWith(pathPrefix)) continue;
|
|
192
203
|
|
|
@@ -267,10 +278,12 @@ async function _executeNostrGetAll(client, pathPrefix, kind, authors, timeout, l
|
|
|
267
278
|
|
|
268
279
|
const events = await client.query(filter, { timeout });
|
|
269
280
|
|
|
270
|
-
// Filter by path prefix
|
|
281
|
+
// Filter by path prefix AND verify author (relays may not respect authors filter)
|
|
271
282
|
const matching = events.filter(event => {
|
|
272
283
|
const dTag = event.tags.find(t => t[0] === 'd');
|
|
273
|
-
|
|
284
|
+
const pathMatches = dTag && dTag[1] && dTag[1].startsWith(pathPrefix);
|
|
285
|
+
const authorAllowed = authors.includes(event.pubkey);
|
|
286
|
+
return pathMatches && authorAllowed;
|
|
274
287
|
});
|
|
275
288
|
|
|
276
289
|
// Parse content and group by d-tag (keep latest only, across all authors)
|
|
@@ -331,10 +344,12 @@ export async function nostrGetAllHybrid(client, pathPrefix, kind = 30000, option
|
|
|
331
344
|
|
|
332
345
|
const events = await queryMethod.call(client, filter, { timeout });
|
|
333
346
|
|
|
334
|
-
// Filter by path prefix
|
|
347
|
+
// Filter by path prefix AND verify author (relays may not respect authors filter)
|
|
335
348
|
const matching = events.filter(event => {
|
|
336
349
|
const dTag = event.tags.find(t => t[0] === 'd');
|
|
337
|
-
|
|
350
|
+
const pathMatches = dTag && dTag[1] && dTag[1].startsWith(pathPrefix);
|
|
351
|
+
const authorAllowed = authors.includes(event.pubkey);
|
|
352
|
+
return pathMatches && authorAllowed;
|
|
338
353
|
});
|
|
339
354
|
|
|
340
355
|
// Parse content and group by d-tag (keep latest only)
|
|
@@ -27,7 +27,11 @@ export function buildPath(appName, holonId, lensName, key = null) {
|
|
|
27
27
|
* @returns {Promise<boolean>} Success indicator
|
|
28
28
|
*/
|
|
29
29
|
export async function write(client, path, data) {
|
|
30
|
-
// Check if this is a GunDB client
|
|
30
|
+
// Check if this is a GunDB client with backend methods (preferred - has write cache)
|
|
31
|
+
if (client.write && client.gun) {
|
|
32
|
+
return client.write(path, data);
|
|
33
|
+
}
|
|
34
|
+
// Fallback to direct gunWrapper (no write cache)
|
|
31
35
|
if (client.gun) {
|
|
32
36
|
return gunWrapper.write(client.gun, path, data);
|
|
33
37
|
}
|
|
@@ -43,6 +47,11 @@ export async function write(client, path, data) {
|
|
|
43
47
|
* @returns {Promise<Object|null>} Data or null
|
|
44
48
|
*/
|
|
45
49
|
export async function read(client, path, options = {}) {
|
|
50
|
+
// Check if this is a GunDB client with backend methods (preferred - has write cache)
|
|
51
|
+
if (client.read && client.gun) {
|
|
52
|
+
return client.read(path, options);
|
|
53
|
+
}
|
|
54
|
+
// Fallback to direct gunWrapper
|
|
46
55
|
if (client.gun) {
|
|
47
56
|
return gunWrapper.read(client.gun, path);
|
|
48
57
|
}
|
|
@@ -57,6 +66,11 @@ export async function read(client, path, options = {}) {
|
|
|
57
66
|
* @returns {Promise<Object[]>} Array of data objects
|
|
58
67
|
*/
|
|
59
68
|
export async function readAll(client, path, options = {}) {
|
|
69
|
+
// Check if this is a GunDB client with backend methods (preferred - has write cache)
|
|
70
|
+
if (client.readAll && client.gun) {
|
|
71
|
+
return client.readAll(path, options);
|
|
72
|
+
}
|
|
73
|
+
// Fallback to direct gunWrapper
|
|
60
74
|
if (client.gun) {
|
|
61
75
|
return gunWrapper.readAll(client.gun, path);
|
|
62
76
|
}
|
|
@@ -71,6 +85,11 @@ export async function readAll(client, path, options = {}) {
|
|
|
71
85
|
* @returns {Promise<boolean>} Success indicator
|
|
72
86
|
*/
|
|
73
87
|
export async function update(client, path, updates) {
|
|
88
|
+
// Check if this is a GunDB client with backend methods (preferred - has write cache)
|
|
89
|
+
if (client.update && client.gun) {
|
|
90
|
+
return client.update(path, updates);
|
|
91
|
+
}
|
|
92
|
+
// Fallback to direct gunWrapper
|
|
74
93
|
if (client.gun) {
|
|
75
94
|
return gunWrapper.update(client.gun, path, updates);
|
|
76
95
|
}
|
|
@@ -84,6 +103,11 @@ export async function update(client, path, updates) {
|
|
|
84
103
|
* @returns {Promise<boolean>} Success indicator
|
|
85
104
|
*/
|
|
86
105
|
export async function deleteData(client, path) {
|
|
106
|
+
// Check if this is a GunDB client with backend methods (preferred - has write cache)
|
|
107
|
+
if (client.delete && client.gun) {
|
|
108
|
+
return client.delete(path);
|
|
109
|
+
}
|
|
110
|
+
// Fallback to direct gunWrapper
|
|
87
111
|
if (client.gun) {
|
|
88
112
|
return gunWrapper.deleteData(client.gun, path);
|
|
89
113
|
}
|
|
@@ -97,6 +121,7 @@ export async function deleteData(client, path) {
|
|
|
97
121
|
* @returns {Promise<Object>} Deletion results
|
|
98
122
|
*/
|
|
99
123
|
export async function deleteAll(client, path) {
|
|
124
|
+
// Fallback to direct gunWrapper (deleteAll not typically in client interface)
|
|
100
125
|
if (client.gun) {
|
|
101
126
|
return gunWrapper.deleteAll(client.gun, path);
|
|
102
127
|
}
|
|
@@ -112,6 +137,11 @@ export async function deleteAll(client, path) {
|
|
|
112
137
|
* @returns {Object} Subscription with unsubscribe method
|
|
113
138
|
*/
|
|
114
139
|
export function subscribe(client, path, callback, options = {}) {
|
|
140
|
+
// Check if this is a GunDB client with backend methods
|
|
141
|
+
if (client.subscribe && client.gun) {
|
|
142
|
+
return client.subscribe(path, callback, options);
|
|
143
|
+
}
|
|
144
|
+
// Fallback to direct gunWrapper
|
|
115
145
|
if (client.gun) {
|
|
116
146
|
return gunWrapper.subscribe(client.gun, path, callback, options);
|
|
117
147
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CDN Build Configuration
|
|
6
|
+
* Creates a self-contained bundle for browser usage via CDN
|
|
7
|
+
* All dependencies are bundled (no externals)
|
|
8
|
+
*/
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
build: {
|
|
11
|
+
target: 'es2020',
|
|
12
|
+
outDir: 'dist/cdn',
|
|
13
|
+
lib: {
|
|
14
|
+
entry: resolve(__dirname, 'src/cdn-entry.js'),
|
|
15
|
+
name: 'HoloSphere',
|
|
16
|
+
formats: ['iife'],
|
|
17
|
+
fileName: () => 'holosphere.min.js',
|
|
18
|
+
},
|
|
19
|
+
rollupOptions: {
|
|
20
|
+
// Bundle all dependencies for CDN
|
|
21
|
+
external: [],
|
|
22
|
+
output: {
|
|
23
|
+
// Use default export so HoloSphere is directly accessible
|
|
24
|
+
exports: 'default',
|
|
25
|
+
// Extend window with all exports
|
|
26
|
+
extend: true,
|
|
27
|
+
// Provide globals for any remaining externals
|
|
28
|
+
globals: {},
|
|
29
|
+
// Inline dynamic imports
|
|
30
|
+
inlineDynamicImports: true,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
sourcemap: true,
|
|
34
|
+
minify: 'terser',
|
|
35
|
+
terserOptions: {
|
|
36
|
+
compress: {
|
|
37
|
+
drop_console: false,
|
|
38
|
+
passes: 2,
|
|
39
|
+
},
|
|
40
|
+
mangle: {
|
|
41
|
+
safari10: true,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
// Increase chunk size warning limit for CDN bundle
|
|
45
|
+
chunkSizeWarningLimit: 2000,
|
|
46
|
+
},
|
|
47
|
+
resolve: {
|
|
48
|
+
alias: [
|
|
49
|
+
{
|
|
50
|
+
find: './filesystem-storage.js',
|
|
51
|
+
replacement: resolve(__dirname, 'src/storage/filesystem-storage-browser.js'),
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
define: {
|
|
56
|
+
'process.versions.node': JSON.stringify(undefined),
|
|
57
|
+
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
58
|
+
'global': 'globalThis',
|
|
59
|
+
},
|
|
60
|
+
});
|