@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
|
@@ -195,12 +195,30 @@ interface TransportProvider extends BaseProvider {
|
|
|
195
195
|
* @returns Unsubscribe function
|
|
196
196
|
*/
|
|
197
197
|
onInstantSplitReceived?(handler: InstantSplitBundleHandler): () => void;
|
|
198
|
+
/**
|
|
199
|
+
* Set fallback 'since' timestamp for event subscriptions.
|
|
200
|
+
* Used when switching to an address that has never subscribed before.
|
|
201
|
+
* The transport uses this instead of 'now' as the initial since filter,
|
|
202
|
+
* ensuring events sent while the address was inactive are not missed.
|
|
203
|
+
* Consumed once by the next subscription setup, then cleared.
|
|
204
|
+
*
|
|
205
|
+
* @param sinceSeconds - Unix timestamp in seconds
|
|
206
|
+
*/
|
|
207
|
+
setFallbackSince?(sinceSeconds: number): void;
|
|
198
208
|
/**
|
|
199
209
|
* Fetch pending events from transport (one-shot query).
|
|
200
210
|
* Creates a temporary subscription, processes events through normal handlers,
|
|
201
211
|
* and resolves after EOSE (End Of Stored Events).
|
|
202
212
|
*/
|
|
203
213
|
fetchPendingEvents?(): Promise<void>;
|
|
214
|
+
/**
|
|
215
|
+
* Register a handler to be called when the chat subscription receives EOSE
|
|
216
|
+
* (End Of Stored Events), indicating that historical DMs have been delivered.
|
|
217
|
+
* The handler fires at most once per subscription lifecycle.
|
|
218
|
+
*
|
|
219
|
+
* @returns Unsubscribe function
|
|
220
|
+
*/
|
|
221
|
+
onChatReady?(handler: () => void): () => void;
|
|
204
222
|
}
|
|
205
223
|
/**
|
|
206
224
|
* Payload for sending instant split bundles
|
|
@@ -468,6 +486,8 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
468
486
|
private storage;
|
|
469
487
|
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
470
488
|
private lastEventTs;
|
|
489
|
+
/** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
|
|
490
|
+
private fallbackSince;
|
|
471
491
|
private identity;
|
|
472
492
|
private keyManager;
|
|
473
493
|
private status;
|
|
@@ -485,6 +505,25 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
485
505
|
private broadcastHandlers;
|
|
486
506
|
private eventCallbacks;
|
|
487
507
|
constructor(config: NostrTransportProviderConfig);
|
|
508
|
+
/**
|
|
509
|
+
* Get the WebSocket factory (used by MultiAddressTransportMux to share the same factory).
|
|
510
|
+
*/
|
|
511
|
+
getWebSocketFactory(): WebSocketFactory;
|
|
512
|
+
/**
|
|
513
|
+
* Get the configured relay URLs.
|
|
514
|
+
*/
|
|
515
|
+
getConfiguredRelays(): string[];
|
|
516
|
+
/**
|
|
517
|
+
* Get the storage adapter.
|
|
518
|
+
*/
|
|
519
|
+
getStorageAdapter(): TransportStorageAdapter | null;
|
|
520
|
+
/**
|
|
521
|
+
* Suppress event subscriptions — unsubscribe wallet/chat filters
|
|
522
|
+
* but keep the connection alive for resolve/identity-binding operations.
|
|
523
|
+
* Used when MultiAddressTransportMux takes over event handling.
|
|
524
|
+
*/
|
|
525
|
+
suppressSubscriptions(): void;
|
|
526
|
+
private _subscriptionsSuppressed;
|
|
488
527
|
connect(): Promise<void>;
|
|
489
528
|
disconnect(): Promise<void>;
|
|
490
529
|
isConnected(): boolean;
|
|
@@ -518,6 +557,7 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
518
557
|
*/
|
|
519
558
|
isRelayConnected(relayUrl: string): boolean;
|
|
520
559
|
setIdentity(identity: FullIdentity): Promise<void>;
|
|
560
|
+
setFallbackSince(sinceSeconds: number): void;
|
|
521
561
|
/**
|
|
522
562
|
* Get the Nostr-format public key (32 bytes / 64 hex chars)
|
|
523
563
|
* This is the x-coordinate only, without the 02/03 prefix.
|
|
@@ -535,6 +575,7 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
535
575
|
onReadReceipt(handler: ReadReceiptHandler): () => void;
|
|
536
576
|
sendTypingIndicator(recipientTransportPubkey: string): Promise<void>;
|
|
537
577
|
onTypingIndicator(handler: TypingIndicatorHandler): () => void;
|
|
578
|
+
onChatReady(handler: () => void): () => void;
|
|
538
579
|
onComposing(handler: ComposingHandler): () => void;
|
|
539
580
|
sendComposingIndicator(recipientPubkey: string, content: string): Promise<void>;
|
|
540
581
|
/**
|
|
@@ -604,6 +645,8 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
604
645
|
private queryEvents;
|
|
605
646
|
private walletSubscriptionId;
|
|
606
647
|
private chatSubscriptionId;
|
|
648
|
+
private chatEoseHandlers;
|
|
649
|
+
private chatEoseFired;
|
|
607
650
|
private subscribeToEvents;
|
|
608
651
|
private subscribeToTags;
|
|
609
652
|
private decryptContent;
|
|
@@ -1122,6 +1165,13 @@ interface TokenStorageProvider<TData = unknown> extends BaseProvider {
|
|
|
1122
1165
|
* Clear all data
|
|
1123
1166
|
*/
|
|
1124
1167
|
clear?(): Promise<boolean>;
|
|
1168
|
+
/**
|
|
1169
|
+
* Create a new independent instance of this provider for a different address.
|
|
1170
|
+
* Used by per-address module architecture — each address gets its own
|
|
1171
|
+
* TokenStorageProvider instance to avoid cross-address data contamination.
|
|
1172
|
+
* If not implemented, the provider cannot be used in multi-address mode.
|
|
1173
|
+
*/
|
|
1174
|
+
createForAddress?(): TokenStorageProvider<TData>;
|
|
1125
1175
|
/**
|
|
1126
1176
|
* Subscribe to storage events
|
|
1127
1177
|
*/
|
|
@@ -1271,6 +1321,10 @@ declare class FileTokenStorageProvider implements TokenStorageProvider<TxfStorag
|
|
|
1271
1321
|
hasHistoryEntry(dedupKey: string): Promise<boolean>;
|
|
1272
1322
|
clearHistory(): Promise<void>;
|
|
1273
1323
|
importHistoryEntries(entries: HistoryRecord[]): Promise<number>;
|
|
1324
|
+
/**
|
|
1325
|
+
* Create an independent instance for a different address.
|
|
1326
|
+
*/
|
|
1327
|
+
createForAddress(): FileTokenStorageProvider;
|
|
1274
1328
|
}
|
|
1275
1329
|
declare function createFileTokenStorageProvider(config: FileTokenStorageConfig | string): FileTokenStorageProvider;
|
|
1276
1330
|
|
|
@@ -195,12 +195,30 @@ interface TransportProvider extends BaseProvider {
|
|
|
195
195
|
* @returns Unsubscribe function
|
|
196
196
|
*/
|
|
197
197
|
onInstantSplitReceived?(handler: InstantSplitBundleHandler): () => void;
|
|
198
|
+
/**
|
|
199
|
+
* Set fallback 'since' timestamp for event subscriptions.
|
|
200
|
+
* Used when switching to an address that has never subscribed before.
|
|
201
|
+
* The transport uses this instead of 'now' as the initial since filter,
|
|
202
|
+
* ensuring events sent while the address was inactive are not missed.
|
|
203
|
+
* Consumed once by the next subscription setup, then cleared.
|
|
204
|
+
*
|
|
205
|
+
* @param sinceSeconds - Unix timestamp in seconds
|
|
206
|
+
*/
|
|
207
|
+
setFallbackSince?(sinceSeconds: number): void;
|
|
198
208
|
/**
|
|
199
209
|
* Fetch pending events from transport (one-shot query).
|
|
200
210
|
* Creates a temporary subscription, processes events through normal handlers,
|
|
201
211
|
* and resolves after EOSE (End Of Stored Events).
|
|
202
212
|
*/
|
|
203
213
|
fetchPendingEvents?(): Promise<void>;
|
|
214
|
+
/**
|
|
215
|
+
* Register a handler to be called when the chat subscription receives EOSE
|
|
216
|
+
* (End Of Stored Events), indicating that historical DMs have been delivered.
|
|
217
|
+
* The handler fires at most once per subscription lifecycle.
|
|
218
|
+
*
|
|
219
|
+
* @returns Unsubscribe function
|
|
220
|
+
*/
|
|
221
|
+
onChatReady?(handler: () => void): () => void;
|
|
204
222
|
}
|
|
205
223
|
/**
|
|
206
224
|
* Payload for sending instant split bundles
|
|
@@ -468,6 +486,8 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
468
486
|
private storage;
|
|
469
487
|
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
470
488
|
private lastEventTs;
|
|
489
|
+
/** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
|
|
490
|
+
private fallbackSince;
|
|
471
491
|
private identity;
|
|
472
492
|
private keyManager;
|
|
473
493
|
private status;
|
|
@@ -485,6 +505,25 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
485
505
|
private broadcastHandlers;
|
|
486
506
|
private eventCallbacks;
|
|
487
507
|
constructor(config: NostrTransportProviderConfig);
|
|
508
|
+
/**
|
|
509
|
+
* Get the WebSocket factory (used by MultiAddressTransportMux to share the same factory).
|
|
510
|
+
*/
|
|
511
|
+
getWebSocketFactory(): WebSocketFactory;
|
|
512
|
+
/**
|
|
513
|
+
* Get the configured relay URLs.
|
|
514
|
+
*/
|
|
515
|
+
getConfiguredRelays(): string[];
|
|
516
|
+
/**
|
|
517
|
+
* Get the storage adapter.
|
|
518
|
+
*/
|
|
519
|
+
getStorageAdapter(): TransportStorageAdapter | null;
|
|
520
|
+
/**
|
|
521
|
+
* Suppress event subscriptions — unsubscribe wallet/chat filters
|
|
522
|
+
* but keep the connection alive for resolve/identity-binding operations.
|
|
523
|
+
* Used when MultiAddressTransportMux takes over event handling.
|
|
524
|
+
*/
|
|
525
|
+
suppressSubscriptions(): void;
|
|
526
|
+
private _subscriptionsSuppressed;
|
|
488
527
|
connect(): Promise<void>;
|
|
489
528
|
disconnect(): Promise<void>;
|
|
490
529
|
isConnected(): boolean;
|
|
@@ -518,6 +557,7 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
518
557
|
*/
|
|
519
558
|
isRelayConnected(relayUrl: string): boolean;
|
|
520
559
|
setIdentity(identity: FullIdentity): Promise<void>;
|
|
560
|
+
setFallbackSince(sinceSeconds: number): void;
|
|
521
561
|
/**
|
|
522
562
|
* Get the Nostr-format public key (32 bytes / 64 hex chars)
|
|
523
563
|
* This is the x-coordinate only, without the 02/03 prefix.
|
|
@@ -535,6 +575,7 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
535
575
|
onReadReceipt(handler: ReadReceiptHandler): () => void;
|
|
536
576
|
sendTypingIndicator(recipientTransportPubkey: string): Promise<void>;
|
|
537
577
|
onTypingIndicator(handler: TypingIndicatorHandler): () => void;
|
|
578
|
+
onChatReady(handler: () => void): () => void;
|
|
538
579
|
onComposing(handler: ComposingHandler): () => void;
|
|
539
580
|
sendComposingIndicator(recipientPubkey: string, content: string): Promise<void>;
|
|
540
581
|
/**
|
|
@@ -604,6 +645,8 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
604
645
|
private queryEvents;
|
|
605
646
|
private walletSubscriptionId;
|
|
606
647
|
private chatSubscriptionId;
|
|
648
|
+
private chatEoseHandlers;
|
|
649
|
+
private chatEoseFired;
|
|
607
650
|
private subscribeToEvents;
|
|
608
651
|
private subscribeToTags;
|
|
609
652
|
private decryptContent;
|
|
@@ -1122,6 +1165,13 @@ interface TokenStorageProvider<TData = unknown> extends BaseProvider {
|
|
|
1122
1165
|
* Clear all data
|
|
1123
1166
|
*/
|
|
1124
1167
|
clear?(): Promise<boolean>;
|
|
1168
|
+
/**
|
|
1169
|
+
* Create a new independent instance of this provider for a different address.
|
|
1170
|
+
* Used by per-address module architecture — each address gets its own
|
|
1171
|
+
* TokenStorageProvider instance to avoid cross-address data contamination.
|
|
1172
|
+
* If not implemented, the provider cannot be used in multi-address mode.
|
|
1173
|
+
*/
|
|
1174
|
+
createForAddress?(): TokenStorageProvider<TData>;
|
|
1125
1175
|
/**
|
|
1126
1176
|
* Subscribe to storage events
|
|
1127
1177
|
*/
|
|
@@ -1271,6 +1321,10 @@ declare class FileTokenStorageProvider implements TokenStorageProvider<TxfStorag
|
|
|
1271
1321
|
hasHistoryEntry(dedupKey: string): Promise<boolean>;
|
|
1272
1322
|
clearHistory(): Promise<void>;
|
|
1273
1323
|
importHistoryEntries(entries: HistoryRecord[]): Promise<number>;
|
|
1324
|
+
/**
|
|
1325
|
+
* Create an independent instance for a different address.
|
|
1326
|
+
*/
|
|
1327
|
+
createForAddress(): FileTokenStorageProvider;
|
|
1274
1328
|
}
|
|
1275
1329
|
declare function createFileTokenStorageProvider(config: FileTokenStorageConfig | string): FileTokenStorageProvider;
|
|
1276
1330
|
|
|
@@ -319,7 +319,7 @@ import * as path2 from "path";
|
|
|
319
319
|
var META_FILE = "_meta.json";
|
|
320
320
|
var TOMBSTONES_FILE = "_tombstones.json";
|
|
321
321
|
var HISTORY_FILE = "_history.json";
|
|
322
|
-
var FileTokenStorageProvider = class {
|
|
322
|
+
var FileTokenStorageProvider = class _FileTokenStorageProvider {
|
|
323
323
|
id = "file-token-storage";
|
|
324
324
|
name = "File Token Storage";
|
|
325
325
|
type = "local";
|
|
@@ -546,6 +546,12 @@ var FileTokenStorageProvider = class {
|
|
|
546
546
|
}
|
|
547
547
|
return imported;
|
|
548
548
|
}
|
|
549
|
+
/**
|
|
550
|
+
* Create an independent instance for a different address.
|
|
551
|
+
*/
|
|
552
|
+
createForAddress() {
|
|
553
|
+
return new _FileTokenStorageProvider({ tokensDir: this.baseTokensDir });
|
|
554
|
+
}
|
|
549
555
|
};
|
|
550
556
|
function createFileTokenStorageProvider(config) {
|
|
551
557
|
return new FileTokenStorageProvider(config);
|
|
@@ -1081,6 +1087,8 @@ var NostrTransportProvider = class {
|
|
|
1081
1087
|
storage = null;
|
|
1082
1088
|
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
1083
1089
|
lastEventTs = 0;
|
|
1090
|
+
/** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
|
|
1091
|
+
fallbackSince = null;
|
|
1084
1092
|
identity = null;
|
|
1085
1093
|
keyManager = null;
|
|
1086
1094
|
status = "disconnected";
|
|
@@ -1113,6 +1121,48 @@ var NostrTransportProvider = class {
|
|
|
1113
1121
|
};
|
|
1114
1122
|
this.storage = config.storage ?? null;
|
|
1115
1123
|
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Get the WebSocket factory (used by MultiAddressTransportMux to share the same factory).
|
|
1126
|
+
*/
|
|
1127
|
+
getWebSocketFactory() {
|
|
1128
|
+
return this.config.createWebSocket;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Get the configured relay URLs.
|
|
1132
|
+
*/
|
|
1133
|
+
getConfiguredRelays() {
|
|
1134
|
+
return [...this.config.relays];
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Get the storage adapter.
|
|
1138
|
+
*/
|
|
1139
|
+
getStorageAdapter() {
|
|
1140
|
+
return this.storage;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Suppress event subscriptions — unsubscribe wallet/chat filters
|
|
1144
|
+
* but keep the connection alive for resolve/identity-binding operations.
|
|
1145
|
+
* Used when MultiAddressTransportMux takes over event handling.
|
|
1146
|
+
*/
|
|
1147
|
+
suppressSubscriptions() {
|
|
1148
|
+
if (!this.nostrClient) return;
|
|
1149
|
+
if (this.walletSubscriptionId) {
|
|
1150
|
+
this.nostrClient.unsubscribe(this.walletSubscriptionId);
|
|
1151
|
+
this.walletSubscriptionId = null;
|
|
1152
|
+
}
|
|
1153
|
+
if (this.chatSubscriptionId) {
|
|
1154
|
+
this.nostrClient.unsubscribe(this.chatSubscriptionId);
|
|
1155
|
+
this.chatSubscriptionId = null;
|
|
1156
|
+
}
|
|
1157
|
+
if (this.mainSubscriptionId) {
|
|
1158
|
+
this.nostrClient.unsubscribe(this.mainSubscriptionId);
|
|
1159
|
+
this.mainSubscriptionId = null;
|
|
1160
|
+
}
|
|
1161
|
+
this._subscriptionsSuppressed = true;
|
|
1162
|
+
logger.debug("Nostr", "Subscriptions suppressed \u2014 mux handles event routing");
|
|
1163
|
+
}
|
|
1164
|
+
// Flag to prevent re-subscription after suppressSubscriptions()
|
|
1165
|
+
_subscriptionsSuppressed = false;
|
|
1116
1166
|
// ===========================================================================
|
|
1117
1167
|
// BaseProvider Implementation
|
|
1118
1168
|
// ===========================================================================
|
|
@@ -1180,6 +1230,7 @@ var NostrTransportProvider = class {
|
|
|
1180
1230
|
this.mainSubscriptionId = null;
|
|
1181
1231
|
this.walletSubscriptionId = null;
|
|
1182
1232
|
this.chatSubscriptionId = null;
|
|
1233
|
+
this.chatEoseFired = false;
|
|
1183
1234
|
this.status = "disconnected";
|
|
1184
1235
|
this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
|
|
1185
1236
|
logger.debug("Nostr", "Disconnected from all relays");
|
|
@@ -1290,6 +1341,8 @@ var NostrTransportProvider = class {
|
|
|
1290
1341
|
// ===========================================================================
|
|
1291
1342
|
async setIdentity(identity) {
|
|
1292
1343
|
this.identity = identity;
|
|
1344
|
+
this.processedEventIds.clear();
|
|
1345
|
+
this.lastEventTs = 0;
|
|
1293
1346
|
const secretKey = Buffer2.from(identity.privateKey, "hex");
|
|
1294
1347
|
this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
|
|
1295
1348
|
const nostrPubkey = this.keyManager.getPublicKeyHex();
|
|
@@ -1332,6 +1385,9 @@ var NostrTransportProvider = class {
|
|
|
1332
1385
|
await this.subscribeToEvents();
|
|
1333
1386
|
}
|
|
1334
1387
|
}
|
|
1388
|
+
setFallbackSince(sinceSeconds) {
|
|
1389
|
+
this.fallbackSince = sinceSeconds;
|
|
1390
|
+
}
|
|
1335
1391
|
/**
|
|
1336
1392
|
* Get the Nostr-format public key (32 bytes / 64 hex chars)
|
|
1337
1393
|
* This is the x-coordinate only, without the 02/03 prefix.
|
|
@@ -1503,6 +1559,20 @@ var NostrTransportProvider = class {
|
|
|
1503
1559
|
this.typingIndicatorHandlers.add(handler);
|
|
1504
1560
|
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1505
1561
|
}
|
|
1562
|
+
onChatReady(handler) {
|
|
1563
|
+
if (this.chatEoseFired) {
|
|
1564
|
+
try {
|
|
1565
|
+
handler();
|
|
1566
|
+
} catch {
|
|
1567
|
+
}
|
|
1568
|
+
return () => {
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
this.chatEoseHandlers.push(handler);
|
|
1572
|
+
return () => {
|
|
1573
|
+
this.chatEoseHandlers = this.chatEoseHandlers.filter((h) => h !== handler);
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1506
1576
|
// ===========================================================================
|
|
1507
1577
|
// Composing Indicators (NIP-59 kind 25050)
|
|
1508
1578
|
// ===========================================================================
|
|
@@ -2220,8 +2290,15 @@ var NostrTransportProvider = class {
|
|
|
2220
2290
|
// Track subscription IDs for cleanup
|
|
2221
2291
|
walletSubscriptionId = null;
|
|
2222
2292
|
chatSubscriptionId = null;
|
|
2293
|
+
// Chat EOSE handlers — fired once when relay finishes delivering stored DMs
|
|
2294
|
+
chatEoseHandlers = [];
|
|
2295
|
+
chatEoseFired = false;
|
|
2223
2296
|
async subscribeToEvents() {
|
|
2224
2297
|
logger.debug("Nostr", "subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
|
|
2298
|
+
if (this._subscriptionsSuppressed) {
|
|
2299
|
+
logger.debug("Nostr", "subscribeToEvents: suppressed \u2014 mux handles event routing");
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2225
2302
|
if (!this.identity || !this.keyManager || !this.nostrClient) {
|
|
2226
2303
|
logger.debug("Nostr", "subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
|
|
2227
2304
|
return;
|
|
@@ -2248,7 +2325,13 @@ var NostrTransportProvider = class {
|
|
|
2248
2325
|
if (stored) {
|
|
2249
2326
|
since = parseInt(stored, 10);
|
|
2250
2327
|
this.lastEventTs = since;
|
|
2328
|
+
this.fallbackSince = null;
|
|
2251
2329
|
logger.debug("Nostr", "Resuming from stored event timestamp:", since);
|
|
2330
|
+
} else if (this.fallbackSince !== null) {
|
|
2331
|
+
since = this.fallbackSince;
|
|
2332
|
+
this.lastEventTs = since;
|
|
2333
|
+
this.fallbackSince = null;
|
|
2334
|
+
logger.debug("Nostr", "Using fallback since timestamp:", since);
|
|
2252
2335
|
} else {
|
|
2253
2336
|
since = Math.floor(Date.now() / 1e3);
|
|
2254
2337
|
logger.debug("Nostr", "No stored timestamp, starting from now:", since);
|
|
@@ -2256,6 +2339,7 @@ var NostrTransportProvider = class {
|
|
|
2256
2339
|
} catch (err) {
|
|
2257
2340
|
logger.debug("Nostr", "Failed to read last event timestamp, falling back to now:", err);
|
|
2258
2341
|
since = Math.floor(Date.now() / 1e3);
|
|
2342
|
+
this.fallbackSince = null;
|
|
2259
2343
|
}
|
|
2260
2344
|
} else {
|
|
2261
2345
|
since = Math.floor(Date.now() / 1e3) - 86400;
|
|
@@ -2309,6 +2393,16 @@ var NostrTransportProvider = class {
|
|
|
2309
2393
|
},
|
|
2310
2394
|
onEndOfStoredEvents: () => {
|
|
2311
2395
|
logger.debug("Nostr", "Chat subscription ready (EOSE)");
|
|
2396
|
+
if (!this.chatEoseFired) {
|
|
2397
|
+
this.chatEoseFired = true;
|
|
2398
|
+
for (const handler of this.chatEoseHandlers) {
|
|
2399
|
+
try {
|
|
2400
|
+
handler();
|
|
2401
|
+
} catch {
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
this.chatEoseHandlers = [];
|
|
2405
|
+
}
|
|
2312
2406
|
},
|
|
2313
2407
|
onError: (_subId, error) => {
|
|
2314
2408
|
logger.debug("Nostr", "Chat subscription error:", error);
|
|
@@ -4213,11 +4307,17 @@ var AsyncSerialQueue = class {
|
|
|
4213
4307
|
var WriteBuffer = class {
|
|
4214
4308
|
/** Full TXF data from save() calls — latest wins */
|
|
4215
4309
|
txfData = null;
|
|
4310
|
+
/** IPNS context captured at save() time — ensures flush writes to the correct
|
|
4311
|
+
* IPNS record even if identity changes between save() and flush(). */
|
|
4312
|
+
capturedIpnsKeyPair = null;
|
|
4313
|
+
capturedIpnsName = null;
|
|
4216
4314
|
get isEmpty() {
|
|
4217
4315
|
return this.txfData === null;
|
|
4218
4316
|
}
|
|
4219
4317
|
clear() {
|
|
4220
4318
|
this.txfData = null;
|
|
4319
|
+
this.capturedIpnsKeyPair = null;
|
|
4320
|
+
this.capturedIpnsName = null;
|
|
4221
4321
|
}
|
|
4222
4322
|
/**
|
|
4223
4323
|
* Merge another buffer's contents into this one (for rollback).
|
|
@@ -4226,12 +4326,14 @@ var WriteBuffer = class {
|
|
|
4226
4326
|
mergeFrom(other) {
|
|
4227
4327
|
if (other.txfData && !this.txfData) {
|
|
4228
4328
|
this.txfData = other.txfData;
|
|
4329
|
+
this.capturedIpnsKeyPair = other.capturedIpnsKeyPair;
|
|
4330
|
+
this.capturedIpnsName = other.capturedIpnsName;
|
|
4229
4331
|
}
|
|
4230
4332
|
}
|
|
4231
4333
|
};
|
|
4232
4334
|
|
|
4233
4335
|
// impl/shared/ipfs/ipfs-storage-provider.ts
|
|
4234
|
-
var IpfsStorageProvider = class {
|
|
4336
|
+
var IpfsStorageProvider = class _IpfsStorageProvider {
|
|
4235
4337
|
id = "ipfs";
|
|
4236
4338
|
name = "IPFS Storage";
|
|
4237
4339
|
type = "p2p";
|
|
@@ -4276,7 +4378,12 @@ var IpfsStorageProvider = class {
|
|
|
4276
4378
|
flushDebounceMs;
|
|
4277
4379
|
/** Set to true during shutdown to prevent new flushes */
|
|
4278
4380
|
isShuttingDown = false;
|
|
4381
|
+
/** Stored config for createForAddress() cloning */
|
|
4382
|
+
_config;
|
|
4383
|
+
_statePersistenceCtor;
|
|
4279
4384
|
constructor(config, statePersistence) {
|
|
4385
|
+
this._config = config;
|
|
4386
|
+
this._statePersistenceCtor = statePersistence;
|
|
4280
4387
|
const gateways = config?.gateways ?? getIpfsGatewayUrls();
|
|
4281
4388
|
this.debug = config?.debug ?? false;
|
|
4282
4389
|
this.ipnsLifetimeMs = config?.ipnsLifetimeMs ?? 99 * 365 * 24 * 60 * 60 * 1e3;
|
|
@@ -4396,6 +4503,7 @@ var IpfsStorageProvider = class {
|
|
|
4396
4503
|
}
|
|
4397
4504
|
async shutdown() {
|
|
4398
4505
|
this.isShuttingDown = true;
|
|
4506
|
+
logger.debug("IPFS-Storage", `shutdown: ipnsName=${this.ipnsName?.slice(0, 20)}..., pendingEmpty=${this.pendingBuffer.isEmpty}, capturedIpns=${this.pendingBuffer.capturedIpnsName?.slice(0, 20) ?? "none"}`);
|
|
4399
4507
|
if (this.flushTimer) {
|
|
4400
4508
|
clearTimeout(this.flushTimer);
|
|
4401
4509
|
this.flushTimer = null;
|
|
@@ -4428,6 +4536,8 @@ var IpfsStorageProvider = class {
|
|
|
4428
4536
|
return { success: false, error: "Not initialized", timestamp: Date.now() };
|
|
4429
4537
|
}
|
|
4430
4538
|
this.pendingBuffer.txfData = data;
|
|
4539
|
+
this.pendingBuffer.capturedIpnsKeyPair = this.ipnsKeyPair;
|
|
4540
|
+
this.pendingBuffer.capturedIpnsName = this.ipnsName;
|
|
4431
4541
|
this.scheduleFlush();
|
|
4432
4542
|
return { success: true, timestamp: Date.now() };
|
|
4433
4543
|
}
|
|
@@ -4438,8 +4548,12 @@ var IpfsStorageProvider = class {
|
|
|
4438
4548
|
* Perform the actual upload + IPNS publish synchronously.
|
|
4439
4549
|
* Called by executeFlush() and sync() — never by public save().
|
|
4440
4550
|
*/
|
|
4441
|
-
async _doSave(data) {
|
|
4442
|
-
|
|
4551
|
+
async _doSave(data, overrideIpns) {
|
|
4552
|
+
const ipnsKeyPair = overrideIpns?.keyPair ?? this.ipnsKeyPair;
|
|
4553
|
+
const ipnsName = overrideIpns?.name ?? this.ipnsName;
|
|
4554
|
+
const metaAddr = data?._meta?.address;
|
|
4555
|
+
logger.debug("IPFS-Storage", `_doSave: ipnsName=${ipnsName?.slice(0, 20)}..., override=${!!overrideIpns}, meta.address=${metaAddr?.slice(0, 20) ?? "none"}`);
|
|
4556
|
+
if (!ipnsKeyPair || !ipnsName) {
|
|
4443
4557
|
return { success: false, error: "Not initialized", timestamp: Date.now() };
|
|
4444
4558
|
}
|
|
4445
4559
|
this.emitEvent({ type: "storage:saving", timestamp: Date.now() });
|
|
@@ -4448,7 +4562,7 @@ var IpfsStorageProvider = class {
|
|
|
4448
4562
|
const metaUpdate = {
|
|
4449
4563
|
...data._meta,
|
|
4450
4564
|
version: this.dataVersion,
|
|
4451
|
-
ipnsName
|
|
4565
|
+
ipnsName,
|
|
4452
4566
|
updatedAt: Date.now()
|
|
4453
4567
|
};
|
|
4454
4568
|
if (this.remoteCid) {
|
|
@@ -4460,13 +4574,13 @@ var IpfsStorageProvider = class {
|
|
|
4460
4574
|
const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
|
|
4461
4575
|
const newSeq = baseSeq + 1n;
|
|
4462
4576
|
const marshalledRecord = await createSignedRecord(
|
|
4463
|
-
|
|
4577
|
+
ipnsKeyPair,
|
|
4464
4578
|
cid,
|
|
4465
4579
|
newSeq,
|
|
4466
4580
|
this.ipnsLifetimeMs
|
|
4467
4581
|
);
|
|
4468
4582
|
const publishResult = await this.httpClient.publishIpns(
|
|
4469
|
-
|
|
4583
|
+
ipnsName,
|
|
4470
4584
|
marshalledRecord
|
|
4471
4585
|
);
|
|
4472
4586
|
if (!publishResult.success) {
|
|
@@ -4481,14 +4595,14 @@ var IpfsStorageProvider = class {
|
|
|
4481
4595
|
this.ipnsSequenceNumber = newSeq;
|
|
4482
4596
|
this.lastCid = cid;
|
|
4483
4597
|
this.remoteCid = cid;
|
|
4484
|
-
this.cache.setIpnsRecord(
|
|
4598
|
+
this.cache.setIpnsRecord(ipnsName, {
|
|
4485
4599
|
cid,
|
|
4486
4600
|
sequence: newSeq,
|
|
4487
4601
|
gateway: "local"
|
|
4488
4602
|
});
|
|
4489
4603
|
this.cache.setContent(cid, updatedData);
|
|
4490
|
-
this.cache.markIpnsFresh(
|
|
4491
|
-
await this.statePersistence.save(
|
|
4604
|
+
this.cache.markIpnsFresh(ipnsName);
|
|
4605
|
+
await this.statePersistence.save(ipnsName, {
|
|
4492
4606
|
sequenceNumber: newSeq.toString(),
|
|
4493
4607
|
lastCid: cid,
|
|
4494
4608
|
version: this.dataVersion
|
|
@@ -4540,7 +4654,8 @@ var IpfsStorageProvider = class {
|
|
|
4540
4654
|
const baseData = active.txfData ?? {
|
|
4541
4655
|
_meta: { version: 0, address: this.identity?.directAddress ?? "", formatVersion: "2.0", updatedAt: 0 }
|
|
4542
4656
|
};
|
|
4543
|
-
const
|
|
4657
|
+
const overrideIpns = active.capturedIpnsKeyPair && active.capturedIpnsName ? { keyPair: active.capturedIpnsKeyPair, name: active.capturedIpnsName } : void 0;
|
|
4658
|
+
const result = await this._doSave(baseData, overrideIpns);
|
|
4544
4659
|
if (!result.success) {
|
|
4545
4660
|
throw new SphereError(result.error ?? "Save failed", "STORAGE_ERROR");
|
|
4546
4661
|
}
|
|
@@ -4843,6 +4958,13 @@ var IpfsStorageProvider = class {
|
|
|
4843
4958
|
log(message) {
|
|
4844
4959
|
logger.debug("IPFS-Storage", message);
|
|
4845
4960
|
}
|
|
4961
|
+
/**
|
|
4962
|
+
* Create an independent instance for a different address.
|
|
4963
|
+
* Shares the same gateway/timeout config but has fresh IPNS state.
|
|
4964
|
+
*/
|
|
4965
|
+
createForAddress() {
|
|
4966
|
+
return new _IpfsStorageProvider(this._config, this._statePersistenceCtor);
|
|
4967
|
+
}
|
|
4846
4968
|
};
|
|
4847
4969
|
|
|
4848
4970
|
// impl/nodejs/ipfs/nodejs-ipfs-state-persistence.ts
|