@unicitylabs/sphere-sdk 0.6.1 → 0.6.3
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/core/index.cjs +1371 -52
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +369 -4
- package/dist/core/index.d.ts +369 -4
- package/dist/core/index.js +1377 -48
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +137 -11
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +137 -11
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +38 -10
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +38 -10
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +133 -11
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +54 -0
- package/dist/impl/nodejs/index.d.ts +54 -0
- package/dist/impl/nodejs/index.js +133 -11
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +1354 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -3
- package/dist/index.d.ts +70 -3
- package/dist/index.js +1353 -50
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
|
@@ -368,7 +368,7 @@ var path2 = __toESM(require("path"), 1);
|
|
|
368
368
|
var META_FILE = "_meta.json";
|
|
369
369
|
var TOMBSTONES_FILE = "_tombstones.json";
|
|
370
370
|
var HISTORY_FILE = "_history.json";
|
|
371
|
-
var FileTokenStorageProvider = class {
|
|
371
|
+
var FileTokenStorageProvider = class _FileTokenStorageProvider {
|
|
372
372
|
id = "file-token-storage";
|
|
373
373
|
name = "File Token Storage";
|
|
374
374
|
type = "local";
|
|
@@ -595,6 +595,12 @@ var FileTokenStorageProvider = class {
|
|
|
595
595
|
}
|
|
596
596
|
return imported;
|
|
597
597
|
}
|
|
598
|
+
/**
|
|
599
|
+
* Create an independent instance for a different address.
|
|
600
|
+
*/
|
|
601
|
+
createForAddress() {
|
|
602
|
+
return new _FileTokenStorageProvider({ tokensDir: this.baseTokensDir });
|
|
603
|
+
}
|
|
598
604
|
};
|
|
599
605
|
function createFileTokenStorageProvider(config) {
|
|
600
606
|
return new FileTokenStorageProvider(config);
|
|
@@ -1118,6 +1124,8 @@ var NostrTransportProvider = class {
|
|
|
1118
1124
|
storage = null;
|
|
1119
1125
|
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
1120
1126
|
lastEventTs = 0;
|
|
1127
|
+
/** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
|
|
1128
|
+
fallbackSince = null;
|
|
1121
1129
|
identity = null;
|
|
1122
1130
|
keyManager = null;
|
|
1123
1131
|
status = "disconnected";
|
|
@@ -1150,6 +1158,48 @@ var NostrTransportProvider = class {
|
|
|
1150
1158
|
};
|
|
1151
1159
|
this.storage = config.storage ?? null;
|
|
1152
1160
|
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Get the WebSocket factory (used by MultiAddressTransportMux to share the same factory).
|
|
1163
|
+
*/
|
|
1164
|
+
getWebSocketFactory() {
|
|
1165
|
+
return this.config.createWebSocket;
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Get the configured relay URLs.
|
|
1169
|
+
*/
|
|
1170
|
+
getConfiguredRelays() {
|
|
1171
|
+
return [...this.config.relays];
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Get the storage adapter.
|
|
1175
|
+
*/
|
|
1176
|
+
getStorageAdapter() {
|
|
1177
|
+
return this.storage;
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Suppress event subscriptions — unsubscribe wallet/chat filters
|
|
1181
|
+
* but keep the connection alive for resolve/identity-binding operations.
|
|
1182
|
+
* Used when MultiAddressTransportMux takes over event handling.
|
|
1183
|
+
*/
|
|
1184
|
+
suppressSubscriptions() {
|
|
1185
|
+
if (!this.nostrClient) return;
|
|
1186
|
+
if (this.walletSubscriptionId) {
|
|
1187
|
+
this.nostrClient.unsubscribe(this.walletSubscriptionId);
|
|
1188
|
+
this.walletSubscriptionId = null;
|
|
1189
|
+
}
|
|
1190
|
+
if (this.chatSubscriptionId) {
|
|
1191
|
+
this.nostrClient.unsubscribe(this.chatSubscriptionId);
|
|
1192
|
+
this.chatSubscriptionId = null;
|
|
1193
|
+
}
|
|
1194
|
+
if (this.mainSubscriptionId) {
|
|
1195
|
+
this.nostrClient.unsubscribe(this.mainSubscriptionId);
|
|
1196
|
+
this.mainSubscriptionId = null;
|
|
1197
|
+
}
|
|
1198
|
+
this._subscriptionsSuppressed = true;
|
|
1199
|
+
logger.debug("Nostr", "Subscriptions suppressed \u2014 mux handles event routing");
|
|
1200
|
+
}
|
|
1201
|
+
// Flag to prevent re-subscription after suppressSubscriptions()
|
|
1202
|
+
_subscriptionsSuppressed = false;
|
|
1153
1203
|
// ===========================================================================
|
|
1154
1204
|
// BaseProvider Implementation
|
|
1155
1205
|
// ===========================================================================
|
|
@@ -1217,6 +1267,7 @@ var NostrTransportProvider = class {
|
|
|
1217
1267
|
this.mainSubscriptionId = null;
|
|
1218
1268
|
this.walletSubscriptionId = null;
|
|
1219
1269
|
this.chatSubscriptionId = null;
|
|
1270
|
+
this.chatEoseFired = false;
|
|
1220
1271
|
this.status = "disconnected";
|
|
1221
1272
|
this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
|
|
1222
1273
|
logger.debug("Nostr", "Disconnected from all relays");
|
|
@@ -1327,6 +1378,8 @@ var NostrTransportProvider = class {
|
|
|
1327
1378
|
// ===========================================================================
|
|
1328
1379
|
async setIdentity(identity) {
|
|
1329
1380
|
this.identity = identity;
|
|
1381
|
+
this.processedEventIds.clear();
|
|
1382
|
+
this.lastEventTs = 0;
|
|
1330
1383
|
const secretKey = import_buffer.Buffer.from(identity.privateKey, "hex");
|
|
1331
1384
|
this.keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(secretKey);
|
|
1332
1385
|
const nostrPubkey = this.keyManager.getPublicKeyHex();
|
|
@@ -1369,6 +1422,9 @@ var NostrTransportProvider = class {
|
|
|
1369
1422
|
await this.subscribeToEvents();
|
|
1370
1423
|
}
|
|
1371
1424
|
}
|
|
1425
|
+
setFallbackSince(sinceSeconds) {
|
|
1426
|
+
this.fallbackSince = sinceSeconds;
|
|
1427
|
+
}
|
|
1372
1428
|
/**
|
|
1373
1429
|
* Get the Nostr-format public key (32 bytes / 64 hex chars)
|
|
1374
1430
|
* This is the x-coordinate only, without the 02/03 prefix.
|
|
@@ -1540,6 +1596,20 @@ var NostrTransportProvider = class {
|
|
|
1540
1596
|
this.typingIndicatorHandlers.add(handler);
|
|
1541
1597
|
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1542
1598
|
}
|
|
1599
|
+
onChatReady(handler) {
|
|
1600
|
+
if (this.chatEoseFired) {
|
|
1601
|
+
try {
|
|
1602
|
+
handler();
|
|
1603
|
+
} catch {
|
|
1604
|
+
}
|
|
1605
|
+
return () => {
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
this.chatEoseHandlers.push(handler);
|
|
1609
|
+
return () => {
|
|
1610
|
+
this.chatEoseHandlers = this.chatEoseHandlers.filter((h) => h !== handler);
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1543
1613
|
// ===========================================================================
|
|
1544
1614
|
// Composing Indicators (NIP-59 kind 25050)
|
|
1545
1615
|
// ===========================================================================
|
|
@@ -2257,8 +2327,15 @@ var NostrTransportProvider = class {
|
|
|
2257
2327
|
// Track subscription IDs for cleanup
|
|
2258
2328
|
walletSubscriptionId = null;
|
|
2259
2329
|
chatSubscriptionId = null;
|
|
2330
|
+
// Chat EOSE handlers — fired once when relay finishes delivering stored DMs
|
|
2331
|
+
chatEoseHandlers = [];
|
|
2332
|
+
chatEoseFired = false;
|
|
2260
2333
|
async subscribeToEvents() {
|
|
2261
2334
|
logger.debug("Nostr", "subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
|
|
2335
|
+
if (this._subscriptionsSuppressed) {
|
|
2336
|
+
logger.debug("Nostr", "subscribeToEvents: suppressed \u2014 mux handles event routing");
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2262
2339
|
if (!this.identity || !this.keyManager || !this.nostrClient) {
|
|
2263
2340
|
logger.debug("Nostr", "subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
|
|
2264
2341
|
return;
|
|
@@ -2285,7 +2362,13 @@ var NostrTransportProvider = class {
|
|
|
2285
2362
|
if (stored) {
|
|
2286
2363
|
since = parseInt(stored, 10);
|
|
2287
2364
|
this.lastEventTs = since;
|
|
2365
|
+
this.fallbackSince = null;
|
|
2288
2366
|
logger.debug("Nostr", "Resuming from stored event timestamp:", since);
|
|
2367
|
+
} else if (this.fallbackSince !== null) {
|
|
2368
|
+
since = this.fallbackSince;
|
|
2369
|
+
this.lastEventTs = since;
|
|
2370
|
+
this.fallbackSince = null;
|
|
2371
|
+
logger.debug("Nostr", "Using fallback since timestamp:", since);
|
|
2289
2372
|
} else {
|
|
2290
2373
|
since = Math.floor(Date.now() / 1e3);
|
|
2291
2374
|
logger.debug("Nostr", "No stored timestamp, starting from now:", since);
|
|
@@ -2293,6 +2376,7 @@ var NostrTransportProvider = class {
|
|
|
2293
2376
|
} catch (err) {
|
|
2294
2377
|
logger.debug("Nostr", "Failed to read last event timestamp, falling back to now:", err);
|
|
2295
2378
|
since = Math.floor(Date.now() / 1e3);
|
|
2379
|
+
this.fallbackSince = null;
|
|
2296
2380
|
}
|
|
2297
2381
|
} else {
|
|
2298
2382
|
since = Math.floor(Date.now() / 1e3) - 86400;
|
|
@@ -2346,6 +2430,16 @@ var NostrTransportProvider = class {
|
|
|
2346
2430
|
},
|
|
2347
2431
|
onEndOfStoredEvents: () => {
|
|
2348
2432
|
logger.debug("Nostr", "Chat subscription ready (EOSE)");
|
|
2433
|
+
if (!this.chatEoseFired) {
|
|
2434
|
+
this.chatEoseFired = true;
|
|
2435
|
+
for (const handler of this.chatEoseHandlers) {
|
|
2436
|
+
try {
|
|
2437
|
+
handler();
|
|
2438
|
+
} catch {
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
this.chatEoseHandlers = [];
|
|
2442
|
+
}
|
|
2349
2443
|
},
|
|
2350
2444
|
onError: (_subId, error) => {
|
|
2351
2445
|
logger.debug("Nostr", "Chat subscription error:", error);
|
|
@@ -4250,11 +4344,17 @@ var AsyncSerialQueue = class {
|
|
|
4250
4344
|
var WriteBuffer = class {
|
|
4251
4345
|
/** Full TXF data from save() calls — latest wins */
|
|
4252
4346
|
txfData = null;
|
|
4347
|
+
/** IPNS context captured at save() time — ensures flush writes to the correct
|
|
4348
|
+
* IPNS record even if identity changes between save() and flush(). */
|
|
4349
|
+
capturedIpnsKeyPair = null;
|
|
4350
|
+
capturedIpnsName = null;
|
|
4253
4351
|
get isEmpty() {
|
|
4254
4352
|
return this.txfData === null;
|
|
4255
4353
|
}
|
|
4256
4354
|
clear() {
|
|
4257
4355
|
this.txfData = null;
|
|
4356
|
+
this.capturedIpnsKeyPair = null;
|
|
4357
|
+
this.capturedIpnsName = null;
|
|
4258
4358
|
}
|
|
4259
4359
|
/**
|
|
4260
4360
|
* Merge another buffer's contents into this one (for rollback).
|
|
@@ -4263,12 +4363,14 @@ var WriteBuffer = class {
|
|
|
4263
4363
|
mergeFrom(other) {
|
|
4264
4364
|
if (other.txfData && !this.txfData) {
|
|
4265
4365
|
this.txfData = other.txfData;
|
|
4366
|
+
this.capturedIpnsKeyPair = other.capturedIpnsKeyPair;
|
|
4367
|
+
this.capturedIpnsName = other.capturedIpnsName;
|
|
4266
4368
|
}
|
|
4267
4369
|
}
|
|
4268
4370
|
};
|
|
4269
4371
|
|
|
4270
4372
|
// impl/shared/ipfs/ipfs-storage-provider.ts
|
|
4271
|
-
var IpfsStorageProvider = class {
|
|
4373
|
+
var IpfsStorageProvider = class _IpfsStorageProvider {
|
|
4272
4374
|
id = "ipfs";
|
|
4273
4375
|
name = "IPFS Storage";
|
|
4274
4376
|
type = "p2p";
|
|
@@ -4313,7 +4415,12 @@ var IpfsStorageProvider = class {
|
|
|
4313
4415
|
flushDebounceMs;
|
|
4314
4416
|
/** Set to true during shutdown to prevent new flushes */
|
|
4315
4417
|
isShuttingDown = false;
|
|
4418
|
+
/** Stored config for createForAddress() cloning */
|
|
4419
|
+
_config;
|
|
4420
|
+
_statePersistenceCtor;
|
|
4316
4421
|
constructor(config, statePersistence) {
|
|
4422
|
+
this._config = config;
|
|
4423
|
+
this._statePersistenceCtor = statePersistence;
|
|
4317
4424
|
const gateways = config?.gateways ?? getIpfsGatewayUrls();
|
|
4318
4425
|
this.debug = config?.debug ?? false;
|
|
4319
4426
|
this.ipnsLifetimeMs = config?.ipnsLifetimeMs ?? 99 * 365 * 24 * 60 * 60 * 1e3;
|
|
@@ -4433,6 +4540,7 @@ var IpfsStorageProvider = class {
|
|
|
4433
4540
|
}
|
|
4434
4541
|
async shutdown() {
|
|
4435
4542
|
this.isShuttingDown = true;
|
|
4543
|
+
logger.debug("IPFS-Storage", `shutdown: ipnsName=${this.ipnsName?.slice(0, 20)}..., pendingEmpty=${this.pendingBuffer.isEmpty}, capturedIpns=${this.pendingBuffer.capturedIpnsName?.slice(0, 20) ?? "none"}`);
|
|
4436
4544
|
if (this.flushTimer) {
|
|
4437
4545
|
clearTimeout(this.flushTimer);
|
|
4438
4546
|
this.flushTimer = null;
|
|
@@ -4465,6 +4573,8 @@ var IpfsStorageProvider = class {
|
|
|
4465
4573
|
return { success: false, error: "Not initialized", timestamp: Date.now() };
|
|
4466
4574
|
}
|
|
4467
4575
|
this.pendingBuffer.txfData = data;
|
|
4576
|
+
this.pendingBuffer.capturedIpnsKeyPair = this.ipnsKeyPair;
|
|
4577
|
+
this.pendingBuffer.capturedIpnsName = this.ipnsName;
|
|
4468
4578
|
this.scheduleFlush();
|
|
4469
4579
|
return { success: true, timestamp: Date.now() };
|
|
4470
4580
|
}
|
|
@@ -4475,8 +4585,12 @@ var IpfsStorageProvider = class {
|
|
|
4475
4585
|
* Perform the actual upload + IPNS publish synchronously.
|
|
4476
4586
|
* Called by executeFlush() and sync() — never by public save().
|
|
4477
4587
|
*/
|
|
4478
|
-
async _doSave(data) {
|
|
4479
|
-
|
|
4588
|
+
async _doSave(data, overrideIpns) {
|
|
4589
|
+
const ipnsKeyPair = overrideIpns?.keyPair ?? this.ipnsKeyPair;
|
|
4590
|
+
const ipnsName = overrideIpns?.name ?? this.ipnsName;
|
|
4591
|
+
const metaAddr = data?._meta?.address;
|
|
4592
|
+
logger.debug("IPFS-Storage", `_doSave: ipnsName=${ipnsName?.slice(0, 20)}..., override=${!!overrideIpns}, meta.address=${metaAddr?.slice(0, 20) ?? "none"}`);
|
|
4593
|
+
if (!ipnsKeyPair || !ipnsName) {
|
|
4480
4594
|
return { success: false, error: "Not initialized", timestamp: Date.now() };
|
|
4481
4595
|
}
|
|
4482
4596
|
this.emitEvent({ type: "storage:saving", timestamp: Date.now() });
|
|
@@ -4485,7 +4599,7 @@ var IpfsStorageProvider = class {
|
|
|
4485
4599
|
const metaUpdate = {
|
|
4486
4600
|
...data._meta,
|
|
4487
4601
|
version: this.dataVersion,
|
|
4488
|
-
ipnsName
|
|
4602
|
+
ipnsName,
|
|
4489
4603
|
updatedAt: Date.now()
|
|
4490
4604
|
};
|
|
4491
4605
|
if (this.remoteCid) {
|
|
@@ -4497,13 +4611,13 @@ var IpfsStorageProvider = class {
|
|
|
4497
4611
|
const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
|
|
4498
4612
|
const newSeq = baseSeq + 1n;
|
|
4499
4613
|
const marshalledRecord = await createSignedRecord(
|
|
4500
|
-
|
|
4614
|
+
ipnsKeyPair,
|
|
4501
4615
|
cid,
|
|
4502
4616
|
newSeq,
|
|
4503
4617
|
this.ipnsLifetimeMs
|
|
4504
4618
|
);
|
|
4505
4619
|
const publishResult = await this.httpClient.publishIpns(
|
|
4506
|
-
|
|
4620
|
+
ipnsName,
|
|
4507
4621
|
marshalledRecord
|
|
4508
4622
|
);
|
|
4509
4623
|
if (!publishResult.success) {
|
|
@@ -4518,14 +4632,14 @@ var IpfsStorageProvider = class {
|
|
|
4518
4632
|
this.ipnsSequenceNumber = newSeq;
|
|
4519
4633
|
this.lastCid = cid;
|
|
4520
4634
|
this.remoteCid = cid;
|
|
4521
|
-
this.cache.setIpnsRecord(
|
|
4635
|
+
this.cache.setIpnsRecord(ipnsName, {
|
|
4522
4636
|
cid,
|
|
4523
4637
|
sequence: newSeq,
|
|
4524
4638
|
gateway: "local"
|
|
4525
4639
|
});
|
|
4526
4640
|
this.cache.setContent(cid, updatedData);
|
|
4527
|
-
this.cache.markIpnsFresh(
|
|
4528
|
-
await this.statePersistence.save(
|
|
4641
|
+
this.cache.markIpnsFresh(ipnsName);
|
|
4642
|
+
await this.statePersistence.save(ipnsName, {
|
|
4529
4643
|
sequenceNumber: newSeq.toString(),
|
|
4530
4644
|
lastCid: cid,
|
|
4531
4645
|
version: this.dataVersion
|
|
@@ -4577,7 +4691,8 @@ var IpfsStorageProvider = class {
|
|
|
4577
4691
|
const baseData = active.txfData ?? {
|
|
4578
4692
|
_meta: { version: 0, address: this.identity?.directAddress ?? "", formatVersion: "2.0", updatedAt: 0 }
|
|
4579
4693
|
};
|
|
4580
|
-
const
|
|
4694
|
+
const overrideIpns = active.capturedIpnsKeyPair && active.capturedIpnsName ? { keyPair: active.capturedIpnsKeyPair, name: active.capturedIpnsName } : void 0;
|
|
4695
|
+
const result = await this._doSave(baseData, overrideIpns);
|
|
4581
4696
|
if (!result.success) {
|
|
4582
4697
|
throw new SphereError(result.error ?? "Save failed", "STORAGE_ERROR");
|
|
4583
4698
|
}
|
|
@@ -4880,6 +4995,13 @@ var IpfsStorageProvider = class {
|
|
|
4880
4995
|
log(message) {
|
|
4881
4996
|
logger.debug("IPFS-Storage", message);
|
|
4882
4997
|
}
|
|
4998
|
+
/**
|
|
4999
|
+
* Create an independent instance for a different address.
|
|
5000
|
+
* Shares the same gateway/timeout config but has fresh IPNS state.
|
|
5001
|
+
*/
|
|
5002
|
+
createForAddress() {
|
|
5003
|
+
return new _IpfsStorageProvider(this._config, this._statePersistenceCtor);
|
|
5004
|
+
}
|
|
4883
5005
|
};
|
|
4884
5006
|
|
|
4885
5007
|
// impl/nodejs/ipfs/nodejs-ipfs-state-persistence.ts
|