holosphere 2.0.0-alpha5 → 2.0.0-alpha7
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-Bvwyvd0T.cjs +5 -0
- package/dist/index-Bvwyvd0T.cjs.map +1 -0
- package/dist/{index-BG8FStkt.cjs → index-C-IlLYlk.cjs} +2 -2
- package/dist/{index-BG8FStkt.cjs.map → index-C-IlLYlk.cjs.map} +1 -1
- package/dist/{index-Bbey4GkP.js → index-d6f4RJBM.js} +222 -116
- package/dist/index-d6f4RJBM.js.map +1 -0
- package/dist/{index-Cp3xctq8.js → index-jmTHEbR2.js} +2 -2
- package/dist/{index-Cp3xctq8.js.map → index-jmTHEbR2.js.map} +1 -1
- package/dist/{indexeddb-storage-Bjg84U5R.js → indexeddb-storage-D8kOl0oK.js} +2 -2
- package/dist/{indexeddb-storage-Bjg84U5R.js.map → indexeddb-storage-D8kOl0oK.js.map} +1 -1
- package/dist/{indexeddb-storage-BD70pN7q.cjs → indexeddb-storage-a8GipaDr.cjs} +2 -2
- package/dist/{indexeddb-storage-BD70pN7q.cjs.map → indexeddb-storage-a8GipaDr.cjs.map} +1 -1
- package/dist/{memory-storage-CD0XFayE.js → memory-storage-DBQK622V.js} +2 -2
- package/dist/{memory-storage-CD0XFayE.js.map → memory-storage-DBQK622V.js.map} +1 -1
- package/dist/{memory-storage-DmMyJtOo.cjs → memory-storage-gfRovk2O.cjs} +2 -2
- package/dist/{memory-storage-DmMyJtOo.cjs.map → memory-storage-gfRovk2O.cjs.map} +1 -1
- package/dist/{secp256k1-TcN6vWGh.cjs → secp256k1-BCAPF45D.cjs} +2 -2
- package/dist/{secp256k1-TcN6vWGh.cjs.map → secp256k1-BCAPF45D.cjs.map} +1 -1
- package/dist/{secp256k1-69sS9O-P.js → secp256k1-DYm_CMqW.js} +2 -2
- package/dist/{secp256k1-69sS9O-P.js.map → secp256k1-DYm_CMqW.js.map} +1 -1
- package/package.json +1 -1
- package/src/contracts/abis/Bundle.json +1438 -1435
- package/src/contracts/deployer.js +32 -3
- package/src/federation/handshake.js +13 -5
- package/src/index.js +9 -1
- package/src/storage/gun-async.js +57 -6
- package/src/storage/gun-auth.js +90 -31
- package/src/storage/gun-wrapper.js +42 -48
- package/src/storage/nostr-client.js +5 -2
- package/dist/index-Bbey4GkP.js.map +0 -1
- package/dist/index-hfVGRwSr.cjs +0 -5
- package/dist/index-hfVGRwSr.cjs.map +0 -1
|
@@ -304,6 +304,7 @@ export class ContractDeployer {
|
|
|
304
304
|
|
|
305
305
|
/**
|
|
306
306
|
* Deploy a Bundle contract (2-way split with steepness)
|
|
307
|
+
* This is the simplest way to deploy a holon - no registry needed!
|
|
307
308
|
* @param {string} name - Contract name
|
|
308
309
|
* @param {number} [steepness=500000000000000000n] - Steepness factor (0.5e18 = 50% decay)
|
|
309
310
|
* @param {number} [nZones=6] - Number of zones
|
|
@@ -319,9 +320,17 @@ export class ContractDeployer {
|
|
|
319
320
|
// Default steepness: 0.5e18 (50% decay per zone)
|
|
320
321
|
const steepnessValue = steepness || ethers.parseEther('0.5');
|
|
321
322
|
|
|
323
|
+
// BundleABI now has { abi, bytecode } structure
|
|
324
|
+
const abi = BundleABI.abi || BundleABI;
|
|
325
|
+
const bytecode = BundleABI.bytecode;
|
|
326
|
+
|
|
327
|
+
if (!bytecode) {
|
|
328
|
+
throw new Error('Bundle bytecode not available. Please rebuild contracts.');
|
|
329
|
+
}
|
|
330
|
+
|
|
322
331
|
const result = await this._deployContract(
|
|
323
|
-
|
|
324
|
-
|
|
332
|
+
abi,
|
|
333
|
+
bytecode,
|
|
325
334
|
[deployerAddress, creatorUserId, name, steepnessValue, nZones]
|
|
326
335
|
);
|
|
327
336
|
|
|
@@ -329,10 +338,30 @@ export class ContractDeployer {
|
|
|
329
338
|
...result,
|
|
330
339
|
type: 'Bundle',
|
|
331
340
|
steepness: steepnessValue,
|
|
332
|
-
nZones
|
|
341
|
+
nZones,
|
|
342
|
+
creatorUserId,
|
|
343
|
+
name
|
|
333
344
|
};
|
|
334
345
|
}
|
|
335
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Deploy a Bundle contract directly (simplified 1-click deployment)
|
|
349
|
+
* No registry needed - just deploys and returns the address
|
|
350
|
+
* @param {string} holonId - Unique identifier for the holon
|
|
351
|
+
* @param {string} name - Display name for the holon
|
|
352
|
+
* @param {Object} [options] - Options { steepness, nZones }
|
|
353
|
+
* @returns {Promise<{address, txHash, contract}>}
|
|
354
|
+
*/
|
|
355
|
+
async deployBundleDirect(holonId, name, options = {}) {
|
|
356
|
+
const steepness = options.steepness;
|
|
357
|
+
const nZones = options.nZones || 6;
|
|
358
|
+
|
|
359
|
+
return this.deployBundle(name, steepness, nZones, {
|
|
360
|
+
creatorUserId: holonId,
|
|
361
|
+
...options
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
336
365
|
/**
|
|
337
366
|
* Deploy a TestToken (ERC20 for testing)
|
|
338
367
|
* @param {string} [initialSupply='1000000'] - Initial supply in whole tokens
|
|
@@ -301,8 +301,8 @@ export async function initiateFederationHandshake(holosphere, privateKey, params
|
|
|
301
301
|
|
|
302
302
|
try {
|
|
303
303
|
// Get sender's public key
|
|
304
|
-
const {
|
|
305
|
-
const senderPubKey =
|
|
304
|
+
const { getPublicKey } = await import('../crypto/nostr-utils.js');
|
|
305
|
+
const senderPubKey = getPublicKey(privateKey);
|
|
306
306
|
|
|
307
307
|
// Create federation request
|
|
308
308
|
const request = createFederationRequest({
|
|
@@ -361,10 +361,10 @@ export async function acceptFederationRequest(holosphere, privateKey, params) {
|
|
|
361
361
|
|
|
362
362
|
try {
|
|
363
363
|
// Get responder's public key
|
|
364
|
-
const {
|
|
365
|
-
const responderPubKey =
|
|
364
|
+
const { getPublicKey } = await import('../crypto/nostr-utils.js');
|
|
365
|
+
const responderPubKey = getPublicKey(privateKey);
|
|
366
366
|
|
|
367
|
-
// Add sender as federated partner
|
|
367
|
+
// Add sender as federated partner in Nostr registry
|
|
368
368
|
if (holosphere.client) {
|
|
369
369
|
await addFederatedPartner(holosphere.client, holosphere.config.appName, senderPubKey, {
|
|
370
370
|
alias: request.senderHolonName,
|
|
@@ -379,6 +379,14 @@ export async function acceptFederationRequest(holosphere, privateKey, params) {
|
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
// Create the actual federation record in GunDB storage
|
|
383
|
+
// This is what makes the partner appear in getFederation() / loadFederationData()
|
|
384
|
+
// Store the sender's name so we can display it without reading their settings
|
|
385
|
+
await holosphere.federateHolon(holonId, senderPubKey, {
|
|
386
|
+
lensConfig,
|
|
387
|
+
partnerName: request.senderHolonName
|
|
388
|
+
});
|
|
389
|
+
|
|
382
390
|
// Create and send response
|
|
383
391
|
const response = createFederationResponse({
|
|
384
392
|
requestId: request.requestId,
|
package/src/index.js
CHANGED
|
@@ -798,6 +798,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
798
798
|
if (!Array.isArray(data.inbound)) data.inbound = [];
|
|
799
799
|
if (!Array.isArray(data.outbound)) data.outbound = [];
|
|
800
800
|
if (!data.lensConfig || typeof data.lensConfig !== 'object') data.lensConfig = {};
|
|
801
|
+
if (!data.partnerNames || typeof data.partnerNames !== 'object') data.partnerNames = {};
|
|
801
802
|
|
|
802
803
|
if (!Array.isArray(data.federated)) {
|
|
803
804
|
const allFederated = new Set([...data.inbound, ...data.outbound]);
|
|
@@ -808,7 +809,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
808
809
|
}
|
|
809
810
|
|
|
810
811
|
async federateHolon(sourceHolon, targetHolon, options = {}) {
|
|
811
|
-
const { lensConfig = { inbound: [], outbound: [] } } = options;
|
|
812
|
+
const { lensConfig = { inbound: [], outbound: [] }, partnerName = null } = options;
|
|
812
813
|
|
|
813
814
|
if (sourceHolon === targetHolon) {
|
|
814
815
|
throw new Error('Cannot federate a holon with itself');
|
|
@@ -821,6 +822,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
821
822
|
inbound: [],
|
|
822
823
|
outbound: [],
|
|
823
824
|
lensConfig: {},
|
|
825
|
+
partnerNames: {},
|
|
824
826
|
timestamp: Date.now()
|
|
825
827
|
};
|
|
826
828
|
|
|
@@ -828,11 +830,17 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
828
830
|
if (!Array.isArray(federationData.inbound)) federationData.inbound = [];
|
|
829
831
|
if (!Array.isArray(federationData.outbound)) federationData.outbound = [];
|
|
830
832
|
if (!federationData.lensConfig || typeof federationData.lensConfig !== 'object') federationData.lensConfig = {};
|
|
833
|
+
if (!federationData.partnerNames || typeof federationData.partnerNames !== 'object') federationData.partnerNames = {};
|
|
831
834
|
|
|
832
835
|
if (!federationData.federated.includes(targetHolon)) {
|
|
833
836
|
federationData.federated.push(targetHolon);
|
|
834
837
|
}
|
|
835
838
|
|
|
839
|
+
// Store the partner's name if provided
|
|
840
|
+
if (partnerName) {
|
|
841
|
+
federationData.partnerNames[targetHolon] = partnerName;
|
|
842
|
+
}
|
|
843
|
+
|
|
836
844
|
if (lensConfig.outbound && lensConfig.outbound.length > 0) {
|
|
837
845
|
if (!federationData.outbound.includes(targetHolon)) {
|
|
838
846
|
federationData.outbound.push(targetHolon);
|
package/src/storage/gun-async.js
CHANGED
|
@@ -64,22 +64,73 @@ export function gunPut(gunChain, data, timeout = 1000) {
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Get all items from a Gun map
|
|
67
|
+
* First gets count of items, then collects until count is reached
|
|
67
68
|
* @param {Object} gunChain - Gun chain reference
|
|
68
|
-
* @param {number} timeout - Timeout in ms (default
|
|
69
|
+
* @param {number} timeout - Timeout in ms (fallback, default 5000ms)
|
|
69
70
|
* @returns {Promise<Object>} Promise resolving to map of items
|
|
70
71
|
*/
|
|
71
|
-
export async function gunMap(gunChain, timeout =
|
|
72
|
+
export async function gunMap(gunChain, timeout = 5000) {
|
|
72
73
|
return new Promise((resolve) => {
|
|
73
74
|
const items = {};
|
|
75
|
+
let settled = false;
|
|
76
|
+
let expectedCount = 0;
|
|
77
|
+
let receivedCount = 0;
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
79
|
+
const tryResolve = () => {
|
|
80
|
+
if (settled) return;
|
|
81
|
+
if (expectedCount > 0 && receivedCount >= expectedCount) {
|
|
82
|
+
settled = true;
|
|
83
|
+
resolve(items);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Step 1: Get parent to count expected items
|
|
88
|
+
gunChain.once((parentData) => {
|
|
89
|
+
if (settled) return;
|
|
90
|
+
|
|
91
|
+
if (!parentData) {
|
|
92
|
+
settled = true;
|
|
93
|
+
resolve({});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const keys = Object.keys(parentData).filter(k => k !== '_');
|
|
98
|
+
if (keys.length === 0) {
|
|
99
|
+
settled = true;
|
|
100
|
+
resolve({});
|
|
101
|
+
return;
|
|
78
102
|
}
|
|
103
|
+
|
|
104
|
+
// Pre-collect inline items (not Gun references)
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
const rawItem = parentData[key];
|
|
107
|
+
if (!rawItem) continue;
|
|
108
|
+
|
|
109
|
+
// Skip Gun references - will be fetched via map().once()
|
|
110
|
+
if (typeof rawItem === 'object' && rawItem['#']) continue;
|
|
111
|
+
|
|
112
|
+
items[key] = rawItem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Expected count is ALL keys (map().once() fires for all)
|
|
116
|
+
expectedCount = keys.length;
|
|
117
|
+
|
|
118
|
+
// Step 2: Collect items via map().once(), counting as we go
|
|
119
|
+
gunChain.map().once((data, key) => {
|
|
120
|
+
if (settled || !data || key.startsWith('_')) return;
|
|
121
|
+
// Add/update the item (inline items may be updated by map().once())
|
|
122
|
+
items[key] = data;
|
|
123
|
+
receivedCount++;
|
|
124
|
+
tryResolve();
|
|
125
|
+
});
|
|
79
126
|
});
|
|
80
127
|
|
|
128
|
+
// Fallback timeout
|
|
81
129
|
setTimeout(() => {
|
|
82
|
-
|
|
130
|
+
if (!settled) {
|
|
131
|
+
settled = true;
|
|
132
|
+
resolve(items);
|
|
133
|
+
}
|
|
83
134
|
}, timeout);
|
|
84
135
|
});
|
|
85
136
|
}
|
package/src/storage/gun-auth.js
CHANGED
|
@@ -208,11 +208,12 @@ export class GunAuth {
|
|
|
208
208
|
|
|
209
209
|
/**
|
|
210
210
|
* Read all data from a private lens
|
|
211
|
+
* First gets count of items, then collects until count is reached
|
|
211
212
|
* @param {string} lens - Lens name
|
|
212
|
-
* @param {number} timeout - Timeout in ms
|
|
213
|
+
* @param {number} timeout - Timeout in ms (fallback)
|
|
213
214
|
* @returns {Promise<Object[]>} Array of data objects
|
|
214
215
|
*/
|
|
215
|
-
async readAllPrivate(lens, timeout =
|
|
216
|
+
async readAllPrivate(lens, timeout = 5000) {
|
|
216
217
|
if (!this.isAuthenticated()) {
|
|
217
218
|
throw new Error('Not authenticated');
|
|
218
219
|
}
|
|
@@ -221,42 +222,100 @@ export class GunAuth {
|
|
|
221
222
|
const results = [];
|
|
222
223
|
const seen = new Set();
|
|
223
224
|
const ref = this.getPrivatePath(lens);
|
|
225
|
+
let settled = false;
|
|
226
|
+
let expectedCount = 0;
|
|
227
|
+
let receivedCount = 0;
|
|
228
|
+
|
|
229
|
+
const tryResolve = () => {
|
|
230
|
+
if (settled) return;
|
|
231
|
+
if (expectedCount > 0 && receivedCount >= expectedCount) {
|
|
232
|
+
settled = true;
|
|
233
|
+
resolve(results);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
224
236
|
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
seen.add(key);
|
|
235
|
-
|
|
236
|
-
let parsed = null;
|
|
237
|
-
// Primary: JSON string format (like old holosphere)
|
|
238
|
-
if (typeof data === 'string') {
|
|
239
|
-
try {
|
|
240
|
-
parsed = JSON.parse(data);
|
|
241
|
-
} catch (e) {
|
|
242
|
-
parsed = data;
|
|
243
|
-
}
|
|
244
|
-
// Legacy: _json wrapper format
|
|
245
|
-
} else if (data._json && typeof data._json === 'string') {
|
|
246
|
-
try {
|
|
247
|
-
parsed = JSON.parse(data._json);
|
|
248
|
-
} catch (e) {}
|
|
249
|
-
// Plain object
|
|
250
|
-
} else if (typeof data === 'object') {
|
|
251
|
-
parsed = { ...data };
|
|
252
|
-
delete parsed['_'];
|
|
237
|
+
const parseItem = (data) => {
|
|
238
|
+
if (!data) return null;
|
|
239
|
+
let parsed = null;
|
|
240
|
+
// Primary: JSON string format (like old holosphere)
|
|
241
|
+
if (typeof data === 'string') {
|
|
242
|
+
try {
|
|
243
|
+
parsed = JSON.parse(data);
|
|
244
|
+
} catch (e) {
|
|
245
|
+
parsed = data;
|
|
253
246
|
}
|
|
247
|
+
// Legacy: _json wrapper format
|
|
248
|
+
} else if (data._json && typeof data._json === 'string') {
|
|
249
|
+
try {
|
|
250
|
+
parsed = JSON.parse(data._json);
|
|
251
|
+
} catch (e) {}
|
|
252
|
+
// Plain object
|
|
253
|
+
} else if (typeof data === 'object') {
|
|
254
|
+
parsed = { ...data };
|
|
255
|
+
delete parsed['_'];
|
|
256
|
+
}
|
|
257
|
+
return parsed;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Step 1: Get parent to count expected items
|
|
261
|
+
ref.once((parentData) => {
|
|
262
|
+
if (settled) return;
|
|
254
263
|
|
|
255
|
-
|
|
264
|
+
if (!parentData) {
|
|
265
|
+
settled = true;
|
|
266
|
+
resolve([]);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const keys = Object.keys(parentData).filter(k => k !== '_');
|
|
271
|
+
if (keys.length === 0) {
|
|
272
|
+
settled = true;
|
|
273
|
+
resolve([]);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Pre-parse inline items (not Gun references)
|
|
278
|
+
for (const key of keys) {
|
|
279
|
+
const rawItem = parentData[key];
|
|
280
|
+
if (!rawItem) continue;
|
|
281
|
+
|
|
282
|
+
// Skip Gun references - will be fetched via map().once()
|
|
283
|
+
if (typeof rawItem === 'object' && rawItem['#']) continue;
|
|
284
|
+
|
|
285
|
+
const parsed = parseItem(rawItem);
|
|
286
|
+
if (parsed && !parsed._deleted && !seen.has(key)) {
|
|
287
|
+
seen.add(key);
|
|
256
288
|
results.push(parsed);
|
|
257
289
|
}
|
|
258
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
|
+
}
|
|
307
|
+
receivedCount++;
|
|
308
|
+
tryResolve();
|
|
309
|
+
});
|
|
259
310
|
});
|
|
311
|
+
|
|
312
|
+
// Fallback timeout
|
|
313
|
+
setTimeout(() => {
|
|
314
|
+
if (!settled) {
|
|
315
|
+
settled = true;
|
|
316
|
+
resolve(results);
|
|
317
|
+
}
|
|
318
|
+
}, timeout);
|
|
260
319
|
});
|
|
261
320
|
}
|
|
262
321
|
|
|
@@ -186,20 +186,34 @@ export async function read(gun, path) {
|
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
188
|
* Read all data under a path (lens query)
|
|
189
|
-
*
|
|
190
|
-
* Falls back to .once() with iteration
|
|
189
|
+
* First gets the count of expected items, then collects until count is reached
|
|
191
190
|
* @param {Object} gun - Gun instance
|
|
192
191
|
* @param {string} path - Gun path
|
|
192
|
+
* @param {number} timeout - Maximum timeout in ms (default 5000)
|
|
193
193
|
* @returns {Promise<Object[]>} Array of data objects
|
|
194
194
|
*/
|
|
195
|
-
export async function readAll(gun, path) {
|
|
195
|
+
export async function readAll(gun, path, timeout = 5000) {
|
|
196
196
|
return new Promise((resolve) => {
|
|
197
197
|
const output = new Map();
|
|
198
198
|
let settled = false;
|
|
199
|
+
let expectedCount = 0;
|
|
200
|
+
let receivedCount = 0;
|
|
199
201
|
|
|
200
202
|
const ref = gun.get(path);
|
|
201
203
|
|
|
202
|
-
|
|
204
|
+
const tryResolve = () => {
|
|
205
|
+
if (settled) return;
|
|
206
|
+
if (expectedCount > 0 && receivedCount >= expectedCount) {
|
|
207
|
+
settled = true;
|
|
208
|
+
resolve(Array.from(output.values()));
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const parseItem = (data) => {
|
|
213
|
+
return deserializeFromGun(data);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Step 1: Get the parent data to count expected items
|
|
203
217
|
ref.once((parentData) => {
|
|
204
218
|
if (settled) return;
|
|
205
219
|
|
|
@@ -218,81 +232,61 @@ export async function readAll(gun, path) {
|
|
|
218
232
|
return;
|
|
219
233
|
}
|
|
220
234
|
|
|
221
|
-
//
|
|
222
|
-
|
|
235
|
+
// Count expected items and check for inline data
|
|
236
|
+
const referenceKeys = [];
|
|
237
|
+
|
|
223
238
|
for (const key of keys) {
|
|
224
239
|
const rawItem = parentData[key];
|
|
225
240
|
if (!rawItem) continue;
|
|
226
241
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Check if it's a Gun reference (soul)
|
|
242
|
+
// Check if it's a Gun reference (soul) - need to fetch separately
|
|
230
243
|
if (typeof rawItem === 'object' && rawItem['#']) {
|
|
231
|
-
|
|
244
|
+
referenceKeys.push(key);
|
|
232
245
|
continue;
|
|
233
246
|
}
|
|
234
247
|
|
|
235
|
-
// Try to parse
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
item = JSON.parse(rawItem._json);
|
|
239
|
-
} catch (e) {}
|
|
240
|
-
}
|
|
241
|
-
// Try as direct JSON string
|
|
242
|
-
else if (typeof rawItem === 'string') {
|
|
243
|
-
try {
|
|
244
|
-
item = JSON.parse(rawItem);
|
|
245
|
-
} catch (e) {}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
+
// Try to parse inline data (don't count yet - will count in map().once() phase)
|
|
249
|
+
const item = parseItem(rawItem);
|
|
248
250
|
if (item && item.id && !item._deleted) {
|
|
249
251
|
output.set(item.id, item);
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
254
|
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
+
// Set expected count: ALL keys that could have data (references + inline)
|
|
256
|
+
// We use total keys because map().once() will fire for all of them
|
|
257
|
+
expectedCount = keys.length;
|
|
258
|
+
|
|
259
|
+
// If no keys, we're done (shouldn't happen but be safe)
|
|
260
|
+
if (expectedCount === 0) {
|
|
255
261
|
settled = true;
|
|
256
262
|
resolve(Array.from(output.values()));
|
|
257
263
|
return;
|
|
258
264
|
}
|
|
259
265
|
|
|
260
|
-
//
|
|
266
|
+
// Step 2: Use map().once() to resolve all items, counting as we go
|
|
261
267
|
ref.map().once((data, key) => {
|
|
262
268
|
if (settled || !data || key === '_') return;
|
|
263
269
|
|
|
264
|
-
|
|
265
|
-
if (data._json && typeof data._json === 'string') {
|
|
266
|
-
try {
|
|
267
|
-
item = JSON.parse(data._json);
|
|
268
|
-
} catch (e) {}
|
|
269
|
-
} else if (typeof data === 'string') {
|
|
270
|
-
try {
|
|
271
|
-
item = JSON.parse(data);
|
|
272
|
-
} catch (e) {}
|
|
273
|
-
}
|
|
274
|
-
|
|
270
|
+
const item = parseItem(data);
|
|
275
271
|
if (item && item.id && !item._deleted) {
|
|
276
|
-
output
|
|
272
|
+
// Add to output if not already there (inline items already added)
|
|
273
|
+
if (!output.has(item.id)) {
|
|
274
|
+
output.set(item.id, item);
|
|
275
|
+
}
|
|
277
276
|
}
|
|
277
|
+
// Count every item received (inline or reference)
|
|
278
|
+
receivedCount++;
|
|
279
|
+
tryResolve();
|
|
278
280
|
});
|
|
279
|
-
|
|
280
|
-
// Give map().once() time to complete
|
|
281
|
-
setTimeout(() => {
|
|
282
|
-
if (!settled) {
|
|
283
|
-
settled = true;
|
|
284
|
-
resolve(Array.from(output.values()));
|
|
285
|
-
}
|
|
286
|
-
}, 500);
|
|
287
281
|
});
|
|
288
282
|
|
|
289
|
-
//
|
|
283
|
+
// Fallback timeout in case count-based resolution fails
|
|
290
284
|
setTimeout(() => {
|
|
291
285
|
if (!settled) {
|
|
292
286
|
settled = true;
|
|
293
287
|
resolve(Array.from(output.values()));
|
|
294
288
|
}
|
|
295
|
-
},
|
|
289
|
+
}, timeout);
|
|
296
290
|
});
|
|
297
291
|
}
|
|
298
292
|
|
|
@@ -496,8 +496,11 @@ export class NostrClient {
|
|
|
496
496
|
async _doPublish(event, options = {}) {
|
|
497
497
|
const waitForRelays = options.waitForRelays || false;
|
|
498
498
|
|
|
499
|
-
//
|
|
500
|
-
|
|
499
|
+
// Check if event is already signed (has id and sig)
|
|
500
|
+
// If so, use it as-is; otherwise sign it
|
|
501
|
+
const signedEvent = (event.id && event.sig)
|
|
502
|
+
? event
|
|
503
|
+
: finalizeEvent(event, this.privateKey);
|
|
501
504
|
|
|
502
505
|
// 1. Cache the event locally first (this makes reads instant)
|
|
503
506
|
await this._cacheEvent(signedEvent);
|