@unicitylabs/sphere-sdk 0.6.2 → 0.6.4
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 +4317 -1470
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +350 -3
- package/dist/core/index.d.ts +350 -3
- package/dist/core/index.js +4321 -1452
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +154 -21
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +154 -21
- 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 +150 -21
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +55 -0
- package/dist/impl/nodejs/index.d.ts +55 -0
- package/dist/impl/nodejs/index.js +150 -21
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +4301 -1480
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -2
- package/dist/index.d.ts +43 -2
- package/dist/index.js +4314 -1471
- package/dist/index.js.map +1 -1
- package/dist/l1/index.d.cts +717 -0
- package/dist/l1/index.d.ts +717 -0
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NostrKeyManager, Event } from '@unicitylabs/nostr-js-sdk';
|
|
1
2
|
import { StateTransitionClient } from '@unicitylabs/state-transition-sdk/lib/StateTransitionClient';
|
|
2
3
|
import { AggregatorClient } from '@unicitylabs/state-transition-sdk/lib/api/AggregatorClient';
|
|
3
4
|
import { RootTrustBase } from '@unicitylabs/state-transition-sdk/lib/bft/RootTrustBase';
|
|
@@ -195,6 +196,16 @@ interface TransportProvider extends BaseProvider {
|
|
|
195
196
|
* @returns Unsubscribe function
|
|
196
197
|
*/
|
|
197
198
|
onInstantSplitReceived?(handler: InstantSplitBundleHandler): () => void;
|
|
199
|
+
/**
|
|
200
|
+
* Set fallback 'since' timestamp for event subscriptions.
|
|
201
|
+
* Used when switching to an address that has never subscribed before.
|
|
202
|
+
* The transport uses this instead of 'now' as the initial since filter,
|
|
203
|
+
* ensuring events sent while the address was inactive are not missed.
|
|
204
|
+
* Consumed once by the next subscription setup, then cleared.
|
|
205
|
+
*
|
|
206
|
+
* @param sinceSeconds - Unix timestamp in seconds
|
|
207
|
+
*/
|
|
208
|
+
setFallbackSince?(sinceSeconds: number): void;
|
|
198
209
|
/**
|
|
199
210
|
* Fetch pending events from transport (one-shot query).
|
|
200
211
|
* Creates a temporary subscription, processes events through normal handlers,
|
|
@@ -476,6 +487,8 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
476
487
|
private storage;
|
|
477
488
|
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
478
489
|
private lastEventTs;
|
|
490
|
+
/** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
|
|
491
|
+
private fallbackSince;
|
|
479
492
|
private identity;
|
|
480
493
|
private keyManager;
|
|
481
494
|
private status;
|
|
@@ -493,6 +506,25 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
493
506
|
private broadcastHandlers;
|
|
494
507
|
private eventCallbacks;
|
|
495
508
|
constructor(config: NostrTransportProviderConfig);
|
|
509
|
+
/**
|
|
510
|
+
* Get the WebSocket factory (used by MultiAddressTransportMux to share the same factory).
|
|
511
|
+
*/
|
|
512
|
+
getWebSocketFactory(): WebSocketFactory;
|
|
513
|
+
/**
|
|
514
|
+
* Get the configured relay URLs.
|
|
515
|
+
*/
|
|
516
|
+
getConfiguredRelays(): string[];
|
|
517
|
+
/**
|
|
518
|
+
* Get the storage adapter.
|
|
519
|
+
*/
|
|
520
|
+
getStorageAdapter(): TransportStorageAdapter | null;
|
|
521
|
+
/**
|
|
522
|
+
* Suppress event subscriptions — unsubscribe wallet/chat filters
|
|
523
|
+
* but keep the connection alive for resolve/identity-binding operations.
|
|
524
|
+
* Used when MultiAddressTransportMux takes over event handling.
|
|
525
|
+
*/
|
|
526
|
+
suppressSubscriptions(): void;
|
|
527
|
+
private _subscriptionsSuppressed;
|
|
496
528
|
connect(): Promise<void>;
|
|
497
529
|
disconnect(): Promise<void>;
|
|
498
530
|
isConnected(): boolean;
|
|
@@ -526,6 +558,7 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
526
558
|
*/
|
|
527
559
|
isRelayConnected(relayUrl: string): boolean;
|
|
528
560
|
setIdentity(identity: FullIdentity): Promise<void>;
|
|
561
|
+
setFallbackSince(sinceSeconds: number): void;
|
|
529
562
|
/**
|
|
530
563
|
* Get the Nostr-format public key (32 bytes / 64 hex chars)
|
|
531
564
|
* This is the x-coordinate only, without the 02/03 prefix.
|
|
@@ -624,6 +657,12 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
624
657
|
*/
|
|
625
658
|
private stripContentPrefix;
|
|
626
659
|
private ensureConnected;
|
|
660
|
+
/**
|
|
661
|
+
* Async version of ensureConnected — reconnects if the original transport
|
|
662
|
+
* lost its WebSocket while subscriptions are suppressed (mux handles events).
|
|
663
|
+
* Used by resolve methods which are always async.
|
|
664
|
+
*/
|
|
665
|
+
private ensureConnectedForResolve;
|
|
627
666
|
private ensureReady;
|
|
628
667
|
private emitEvent;
|
|
629
668
|
/**
|
|
@@ -632,6 +671,11 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
632
671
|
* because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
|
|
633
672
|
*/
|
|
634
673
|
private createCustomKindGiftWrap;
|
|
674
|
+
/**
|
|
675
|
+
* Create a NIP-17 gift wrap with a custom rumor kind.
|
|
676
|
+
* Shared between NostrTransportProvider and MultiAddressTransportMux.
|
|
677
|
+
*/
|
|
678
|
+
static createCustomKindGiftWrap(keyManager: NostrKeyManager, recipientPubkeyHex: string, content: string, rumorKind: number): Event;
|
|
635
679
|
}
|
|
636
680
|
|
|
637
681
|
/**
|
|
@@ -1133,6 +1177,13 @@ interface TokenStorageProvider<TData = unknown> extends BaseProvider {
|
|
|
1133
1177
|
* Clear all data
|
|
1134
1178
|
*/
|
|
1135
1179
|
clear?(): Promise<boolean>;
|
|
1180
|
+
/**
|
|
1181
|
+
* Create a new independent instance of this provider for a different address.
|
|
1182
|
+
* Used by per-address module architecture — each address gets its own
|
|
1183
|
+
* TokenStorageProvider instance to avoid cross-address data contamination.
|
|
1184
|
+
* If not implemented, the provider cannot be used in multi-address mode.
|
|
1185
|
+
*/
|
|
1186
|
+
createForAddress?(): TokenStorageProvider<TData>;
|
|
1136
1187
|
/**
|
|
1137
1188
|
* Subscribe to storage events
|
|
1138
1189
|
*/
|
|
@@ -1282,6 +1333,10 @@ declare class FileTokenStorageProvider implements TokenStorageProvider<TxfStorag
|
|
|
1282
1333
|
hasHistoryEntry(dedupKey: string): Promise<boolean>;
|
|
1283
1334
|
clearHistory(): Promise<void>;
|
|
1284
1335
|
importHistoryEntries(entries: HistoryRecord[]): Promise<number>;
|
|
1336
|
+
/**
|
|
1337
|
+
* Create an independent instance for a different address.
|
|
1338
|
+
*/
|
|
1339
|
+
createForAddress(): FileTokenStorageProvider;
|
|
1285
1340
|
}
|
|
1286
1341
|
declare function createFileTokenStorageProvider(config: FileTokenStorageConfig | string): FileTokenStorageProvider;
|
|
1287
1342
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NostrKeyManager, Event } from '@unicitylabs/nostr-js-sdk';
|
|
1
2
|
import { StateTransitionClient } from '@unicitylabs/state-transition-sdk/lib/StateTransitionClient';
|
|
2
3
|
import { AggregatorClient } from '@unicitylabs/state-transition-sdk/lib/api/AggregatorClient';
|
|
3
4
|
import { RootTrustBase } from '@unicitylabs/state-transition-sdk/lib/bft/RootTrustBase';
|
|
@@ -195,6 +196,16 @@ interface TransportProvider extends BaseProvider {
|
|
|
195
196
|
* @returns Unsubscribe function
|
|
196
197
|
*/
|
|
197
198
|
onInstantSplitReceived?(handler: InstantSplitBundleHandler): () => void;
|
|
199
|
+
/**
|
|
200
|
+
* Set fallback 'since' timestamp for event subscriptions.
|
|
201
|
+
* Used when switching to an address that has never subscribed before.
|
|
202
|
+
* The transport uses this instead of 'now' as the initial since filter,
|
|
203
|
+
* ensuring events sent while the address was inactive are not missed.
|
|
204
|
+
* Consumed once by the next subscription setup, then cleared.
|
|
205
|
+
*
|
|
206
|
+
* @param sinceSeconds - Unix timestamp in seconds
|
|
207
|
+
*/
|
|
208
|
+
setFallbackSince?(sinceSeconds: number): void;
|
|
198
209
|
/**
|
|
199
210
|
* Fetch pending events from transport (one-shot query).
|
|
200
211
|
* Creates a temporary subscription, processes events through normal handlers,
|
|
@@ -476,6 +487,8 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
476
487
|
private storage;
|
|
477
488
|
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
478
489
|
private lastEventTs;
|
|
490
|
+
/** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
|
|
491
|
+
private fallbackSince;
|
|
479
492
|
private identity;
|
|
480
493
|
private keyManager;
|
|
481
494
|
private status;
|
|
@@ -493,6 +506,25 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
493
506
|
private broadcastHandlers;
|
|
494
507
|
private eventCallbacks;
|
|
495
508
|
constructor(config: NostrTransportProviderConfig);
|
|
509
|
+
/**
|
|
510
|
+
* Get the WebSocket factory (used by MultiAddressTransportMux to share the same factory).
|
|
511
|
+
*/
|
|
512
|
+
getWebSocketFactory(): WebSocketFactory;
|
|
513
|
+
/**
|
|
514
|
+
* Get the configured relay URLs.
|
|
515
|
+
*/
|
|
516
|
+
getConfiguredRelays(): string[];
|
|
517
|
+
/**
|
|
518
|
+
* Get the storage adapter.
|
|
519
|
+
*/
|
|
520
|
+
getStorageAdapter(): TransportStorageAdapter | null;
|
|
521
|
+
/**
|
|
522
|
+
* Suppress event subscriptions — unsubscribe wallet/chat filters
|
|
523
|
+
* but keep the connection alive for resolve/identity-binding operations.
|
|
524
|
+
* Used when MultiAddressTransportMux takes over event handling.
|
|
525
|
+
*/
|
|
526
|
+
suppressSubscriptions(): void;
|
|
527
|
+
private _subscriptionsSuppressed;
|
|
496
528
|
connect(): Promise<void>;
|
|
497
529
|
disconnect(): Promise<void>;
|
|
498
530
|
isConnected(): boolean;
|
|
@@ -526,6 +558,7 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
526
558
|
*/
|
|
527
559
|
isRelayConnected(relayUrl: string): boolean;
|
|
528
560
|
setIdentity(identity: FullIdentity): Promise<void>;
|
|
561
|
+
setFallbackSince(sinceSeconds: number): void;
|
|
529
562
|
/**
|
|
530
563
|
* Get the Nostr-format public key (32 bytes / 64 hex chars)
|
|
531
564
|
* This is the x-coordinate only, without the 02/03 prefix.
|
|
@@ -624,6 +657,12 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
624
657
|
*/
|
|
625
658
|
private stripContentPrefix;
|
|
626
659
|
private ensureConnected;
|
|
660
|
+
/**
|
|
661
|
+
* Async version of ensureConnected — reconnects if the original transport
|
|
662
|
+
* lost its WebSocket while subscriptions are suppressed (mux handles events).
|
|
663
|
+
* Used by resolve methods which are always async.
|
|
664
|
+
*/
|
|
665
|
+
private ensureConnectedForResolve;
|
|
627
666
|
private ensureReady;
|
|
628
667
|
private emitEvent;
|
|
629
668
|
/**
|
|
@@ -632,6 +671,11 @@ declare class NostrTransportProvider implements TransportProvider {
|
|
|
632
671
|
* because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
|
|
633
672
|
*/
|
|
634
673
|
private createCustomKindGiftWrap;
|
|
674
|
+
/**
|
|
675
|
+
* Create a NIP-17 gift wrap with a custom rumor kind.
|
|
676
|
+
* Shared between NostrTransportProvider and MultiAddressTransportMux.
|
|
677
|
+
*/
|
|
678
|
+
static createCustomKindGiftWrap(keyManager: NostrKeyManager, recipientPubkeyHex: string, content: string, rumorKind: number): Event;
|
|
635
679
|
}
|
|
636
680
|
|
|
637
681
|
/**
|
|
@@ -1133,6 +1177,13 @@ interface TokenStorageProvider<TData = unknown> extends BaseProvider {
|
|
|
1133
1177
|
* Clear all data
|
|
1134
1178
|
*/
|
|
1135
1179
|
clear?(): Promise<boolean>;
|
|
1180
|
+
/**
|
|
1181
|
+
* Create a new independent instance of this provider for a different address.
|
|
1182
|
+
* Used by per-address module architecture — each address gets its own
|
|
1183
|
+
* TokenStorageProvider instance to avoid cross-address data contamination.
|
|
1184
|
+
* If not implemented, the provider cannot be used in multi-address mode.
|
|
1185
|
+
*/
|
|
1186
|
+
createForAddress?(): TokenStorageProvider<TData>;
|
|
1136
1187
|
/**
|
|
1137
1188
|
* Subscribe to storage events
|
|
1138
1189
|
*/
|
|
@@ -1282,6 +1333,10 @@ declare class FileTokenStorageProvider implements TokenStorageProvider<TxfStorag
|
|
|
1282
1333
|
hasHistoryEntry(dedupKey: string): Promise<boolean>;
|
|
1283
1334
|
clearHistory(): Promise<void>;
|
|
1284
1335
|
importHistoryEntries(entries: HistoryRecord[]): Promise<number>;
|
|
1336
|
+
/**
|
|
1337
|
+
* Create an independent instance for a different address.
|
|
1338
|
+
*/
|
|
1339
|
+
createForAddress(): FileTokenStorageProvider;
|
|
1285
1340
|
}
|
|
1286
1341
|
declare function createFileTokenStorageProvider(config: FileTokenStorageConfig | string): FileTokenStorageProvider;
|
|
1287
1342
|
|
|
@@ -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);
|
|
@@ -1072,7 +1078,7 @@ function defaultUUIDGenerator() {
|
|
|
1072
1078
|
var COMPOSING_INDICATOR_KIND = 25050;
|
|
1073
1079
|
var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
|
|
1074
1080
|
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
1075
|
-
var NostrTransportProvider = class {
|
|
1081
|
+
var NostrTransportProvider = class _NostrTransportProvider {
|
|
1076
1082
|
id = "nostr";
|
|
1077
1083
|
name = "Nostr Transport";
|
|
1078
1084
|
type = "p2p";
|
|
@@ -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
|
// ===========================================================================
|
|
@@ -1291,6 +1341,8 @@ var NostrTransportProvider = class {
|
|
|
1291
1341
|
// ===========================================================================
|
|
1292
1342
|
async setIdentity(identity) {
|
|
1293
1343
|
this.identity = identity;
|
|
1344
|
+
this.processedEventIds.clear();
|
|
1345
|
+
this.lastEventTs = 0;
|
|
1294
1346
|
const secretKey = Buffer2.from(identity.privateKey, "hex");
|
|
1295
1347
|
this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
|
|
1296
1348
|
const nostrPubkey = this.keyManager.getPublicKeyHex();
|
|
@@ -1333,6 +1385,9 @@ var NostrTransportProvider = class {
|
|
|
1333
1385
|
await this.subscribeToEvents();
|
|
1334
1386
|
}
|
|
1335
1387
|
}
|
|
1388
|
+
setFallbackSince(sinceSeconds) {
|
|
1389
|
+
this.fallbackSince = sinceSeconds;
|
|
1390
|
+
}
|
|
1336
1391
|
/**
|
|
1337
1392
|
* Get the Nostr-format public key (32 bytes / 64 hex chars)
|
|
1338
1393
|
* This is the x-coordinate only, without the 02/03 prefix.
|
|
@@ -1554,11 +1609,11 @@ var NostrTransportProvider = class {
|
|
|
1554
1609
|
return this.resolveNametagInfo(identifier);
|
|
1555
1610
|
}
|
|
1556
1611
|
async resolveNametag(nametag) {
|
|
1557
|
-
this.
|
|
1612
|
+
await this.ensureConnectedForResolve();
|
|
1558
1613
|
return this.nostrClient.queryPubkeyByNametag(nametag);
|
|
1559
1614
|
}
|
|
1560
1615
|
async resolveNametagInfo(nametag) {
|
|
1561
|
-
this.
|
|
1616
|
+
await this.ensureConnectedForResolve();
|
|
1562
1617
|
const binding = await this.nostrClient.queryBindingByNametag(nametag);
|
|
1563
1618
|
if (!binding) {
|
|
1564
1619
|
logger.debug("Nostr", `resolveNametagInfo: no binding events found for Unicity ID "${nametag}"`);
|
|
@@ -1571,7 +1626,7 @@ var NostrTransportProvider = class {
|
|
|
1571
1626
|
* Performs reverse lookup via nostr-js-sdk with first-seen-wins anti-hijacking.
|
|
1572
1627
|
*/
|
|
1573
1628
|
async resolveAddressInfo(address) {
|
|
1574
|
-
this.
|
|
1629
|
+
await this.ensureConnectedForResolve();
|
|
1575
1630
|
const binding = await this.nostrClient.queryBindingByAddress(address);
|
|
1576
1631
|
if (!binding) return null;
|
|
1577
1632
|
return this.bindingInfoToPeerInfo(binding);
|
|
@@ -1606,7 +1661,7 @@ var NostrTransportProvider = class {
|
|
|
1606
1661
|
* Queries binding events authored by the given pubkey.
|
|
1607
1662
|
*/
|
|
1608
1663
|
async resolveTransportPubkeyInfo(transportPubkey) {
|
|
1609
|
-
this.
|
|
1664
|
+
await this.ensureConnectedForResolve();
|
|
1610
1665
|
const events = await this.queryEvents({
|
|
1611
1666
|
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
1612
1667
|
authors: [transportPubkey],
|
|
@@ -1641,7 +1696,7 @@ var NostrTransportProvider = class {
|
|
|
1641
1696
|
* Used for HD address discovery — single relay query with multi-author filter.
|
|
1642
1697
|
*/
|
|
1643
1698
|
async discoverAddresses(transportPubkeys) {
|
|
1644
|
-
this.
|
|
1699
|
+
await this.ensureConnectedForResolve();
|
|
1645
1700
|
if (transportPubkeys.length === 0) return [];
|
|
1646
1701
|
const events = await this.queryEvents({
|
|
1647
1702
|
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
@@ -1680,7 +1735,10 @@ var NostrTransportProvider = class {
|
|
|
1680
1735
|
* @returns Decrypted nametag or null if none found
|
|
1681
1736
|
*/
|
|
1682
1737
|
async recoverNametag() {
|
|
1683
|
-
this.
|
|
1738
|
+
await this.ensureConnectedForResolve();
|
|
1739
|
+
if (!this.identity) {
|
|
1740
|
+
throw new SphereError("Identity not set", "NOT_INITIALIZED");
|
|
1741
|
+
}
|
|
1684
1742
|
if (!this.identity || !this.keyManager) {
|
|
1685
1743
|
throw new SphereError("Identity not set", "NOT_INITIALIZED");
|
|
1686
1744
|
}
|
|
@@ -2240,6 +2298,10 @@ var NostrTransportProvider = class {
|
|
|
2240
2298
|
chatEoseFired = false;
|
|
2241
2299
|
async subscribeToEvents() {
|
|
2242
2300
|
logger.debug("Nostr", "subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
|
|
2301
|
+
if (this._subscriptionsSuppressed) {
|
|
2302
|
+
logger.debug("Nostr", "subscribeToEvents: suppressed \u2014 mux handles event routing");
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2243
2305
|
if (!this.identity || !this.keyManager || !this.nostrClient) {
|
|
2244
2306
|
logger.debug("Nostr", "subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
|
|
2245
2307
|
return;
|
|
@@ -2266,7 +2328,13 @@ var NostrTransportProvider = class {
|
|
|
2266
2328
|
if (stored) {
|
|
2267
2329
|
since = parseInt(stored, 10);
|
|
2268
2330
|
this.lastEventTs = since;
|
|
2331
|
+
this.fallbackSince = null;
|
|
2269
2332
|
logger.debug("Nostr", "Resuming from stored event timestamp:", since);
|
|
2333
|
+
} else if (this.fallbackSince !== null) {
|
|
2334
|
+
since = this.fallbackSince;
|
|
2335
|
+
this.lastEventTs = since;
|
|
2336
|
+
this.fallbackSince = null;
|
|
2337
|
+
logger.debug("Nostr", "Using fallback since timestamp:", since);
|
|
2270
2338
|
} else {
|
|
2271
2339
|
since = Math.floor(Date.now() / 1e3);
|
|
2272
2340
|
logger.debug("Nostr", "No stored timestamp, starting from now:", since);
|
|
@@ -2274,6 +2342,7 @@ var NostrTransportProvider = class {
|
|
|
2274
2342
|
} catch (err) {
|
|
2275
2343
|
logger.debug("Nostr", "Failed to read last event timestamp, falling back to now:", err);
|
|
2276
2344
|
since = Math.floor(Date.now() / 1e3);
|
|
2345
|
+
this.fallbackSince = null;
|
|
2277
2346
|
}
|
|
2278
2347
|
} else {
|
|
2279
2348
|
since = Math.floor(Date.now() / 1e3) - 86400;
|
|
@@ -2405,6 +2474,31 @@ var NostrTransportProvider = class {
|
|
|
2405
2474
|
throw new SphereError("NostrTransportProvider not connected", "TRANSPORT_ERROR");
|
|
2406
2475
|
}
|
|
2407
2476
|
}
|
|
2477
|
+
/**
|
|
2478
|
+
* Async version of ensureConnected — reconnects if the original transport
|
|
2479
|
+
* lost its WebSocket while subscriptions are suppressed (mux handles events).
|
|
2480
|
+
* Used by resolve methods which are always async.
|
|
2481
|
+
*/
|
|
2482
|
+
async ensureConnectedForResolve() {
|
|
2483
|
+
if (this.isConnected()) return;
|
|
2484
|
+
if (this._subscriptionsSuppressed && this.nostrClient) {
|
|
2485
|
+
logger.debug("Nostr", "Suppressed transport disconnected \u2014 reconnecting for resolve");
|
|
2486
|
+
try {
|
|
2487
|
+
await Promise.race([
|
|
2488
|
+
this.nostrClient.connect(...this.config.relays),
|
|
2489
|
+
new Promise(
|
|
2490
|
+
(_, reject) => setTimeout(() => reject(new Error("reconnect timeout")), 5e3)
|
|
2491
|
+
)
|
|
2492
|
+
]);
|
|
2493
|
+
if (this.nostrClient.isConnected()) {
|
|
2494
|
+
this.status = "connected";
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
} catch {
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
throw new SphereError("NostrTransportProvider not connected", "TRANSPORT_ERROR");
|
|
2501
|
+
}
|
|
2408
2502
|
ensureReady() {
|
|
2409
2503
|
this.ensureConnected();
|
|
2410
2504
|
if (!this.identity) {
|
|
@@ -2426,16 +2520,23 @@ var NostrTransportProvider = class {
|
|
|
2426
2520
|
* because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
|
|
2427
2521
|
*/
|
|
2428
2522
|
createCustomKindGiftWrap(recipientPubkeyHex, content, rumorKind) {
|
|
2429
|
-
|
|
2523
|
+
return _NostrTransportProvider.createCustomKindGiftWrap(this.keyManager, recipientPubkeyHex, content, rumorKind);
|
|
2524
|
+
}
|
|
2525
|
+
/**
|
|
2526
|
+
* Create a NIP-17 gift wrap with a custom rumor kind.
|
|
2527
|
+
* Shared between NostrTransportProvider and MultiAddressTransportMux.
|
|
2528
|
+
*/
|
|
2529
|
+
static createCustomKindGiftWrap(keyManager, recipientPubkeyHex, content, rumorKind) {
|
|
2530
|
+
const senderPubkey = keyManager.getPublicKeyHex();
|
|
2430
2531
|
const now = Math.floor(Date.now() / 1e3);
|
|
2431
2532
|
const rumorTags = [["p", recipientPubkeyHex]];
|
|
2432
2533
|
const rumorSerialized = JSON.stringify([0, senderPubkey, now, rumorKind, rumorTags, content]);
|
|
2433
2534
|
const rumorId = bytesToHex(sha256(new TextEncoder().encode(rumorSerialized)));
|
|
2434
2535
|
const rumor = { id: rumorId, pubkey: senderPubkey, created_at: now, kind: rumorKind, tags: rumorTags, content };
|
|
2435
2536
|
const recipientPubkeyBytes = hexToBytes(recipientPubkeyHex);
|
|
2436
|
-
const encryptedRumor = NIP44.encrypt(JSON.stringify(rumor),
|
|
2537
|
+
const encryptedRumor = NIP44.encrypt(JSON.stringify(rumor), keyManager.getPrivateKey(), recipientPubkeyBytes);
|
|
2437
2538
|
const sealTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
|
|
2438
|
-
const seal = NostrEventClass.create(
|
|
2539
|
+
const seal = NostrEventClass.create(keyManager, {
|
|
2439
2540
|
kind: EventKinds.SEAL,
|
|
2440
2541
|
tags: [],
|
|
2441
2542
|
content: encryptedRumor,
|
|
@@ -4241,11 +4342,17 @@ var AsyncSerialQueue = class {
|
|
|
4241
4342
|
var WriteBuffer = class {
|
|
4242
4343
|
/** Full TXF data from save() calls — latest wins */
|
|
4243
4344
|
txfData = null;
|
|
4345
|
+
/** IPNS context captured at save() time — ensures flush writes to the correct
|
|
4346
|
+
* IPNS record even if identity changes between save() and flush(). */
|
|
4347
|
+
capturedIpnsKeyPair = null;
|
|
4348
|
+
capturedIpnsName = null;
|
|
4244
4349
|
get isEmpty() {
|
|
4245
4350
|
return this.txfData === null;
|
|
4246
4351
|
}
|
|
4247
4352
|
clear() {
|
|
4248
4353
|
this.txfData = null;
|
|
4354
|
+
this.capturedIpnsKeyPair = null;
|
|
4355
|
+
this.capturedIpnsName = null;
|
|
4249
4356
|
}
|
|
4250
4357
|
/**
|
|
4251
4358
|
* Merge another buffer's contents into this one (for rollback).
|
|
@@ -4254,12 +4361,14 @@ var WriteBuffer = class {
|
|
|
4254
4361
|
mergeFrom(other) {
|
|
4255
4362
|
if (other.txfData && !this.txfData) {
|
|
4256
4363
|
this.txfData = other.txfData;
|
|
4364
|
+
this.capturedIpnsKeyPair = other.capturedIpnsKeyPair;
|
|
4365
|
+
this.capturedIpnsName = other.capturedIpnsName;
|
|
4257
4366
|
}
|
|
4258
4367
|
}
|
|
4259
4368
|
};
|
|
4260
4369
|
|
|
4261
4370
|
// impl/shared/ipfs/ipfs-storage-provider.ts
|
|
4262
|
-
var IpfsStorageProvider = class {
|
|
4371
|
+
var IpfsStorageProvider = class _IpfsStorageProvider {
|
|
4263
4372
|
id = "ipfs";
|
|
4264
4373
|
name = "IPFS Storage";
|
|
4265
4374
|
type = "p2p";
|
|
@@ -4304,7 +4413,12 @@ var IpfsStorageProvider = class {
|
|
|
4304
4413
|
flushDebounceMs;
|
|
4305
4414
|
/** Set to true during shutdown to prevent new flushes */
|
|
4306
4415
|
isShuttingDown = false;
|
|
4416
|
+
/** Stored config for createForAddress() cloning */
|
|
4417
|
+
_config;
|
|
4418
|
+
_statePersistenceCtor;
|
|
4307
4419
|
constructor(config, statePersistence) {
|
|
4420
|
+
this._config = config;
|
|
4421
|
+
this._statePersistenceCtor = statePersistence;
|
|
4308
4422
|
const gateways = config?.gateways ?? getIpfsGatewayUrls();
|
|
4309
4423
|
this.debug = config?.debug ?? false;
|
|
4310
4424
|
this.ipnsLifetimeMs = config?.ipnsLifetimeMs ?? 99 * 365 * 24 * 60 * 60 * 1e3;
|
|
@@ -4424,6 +4538,7 @@ var IpfsStorageProvider = class {
|
|
|
4424
4538
|
}
|
|
4425
4539
|
async shutdown() {
|
|
4426
4540
|
this.isShuttingDown = true;
|
|
4541
|
+
logger.debug("IPFS-Storage", `shutdown: ipnsName=${this.ipnsName?.slice(0, 20)}..., pendingEmpty=${this.pendingBuffer.isEmpty}, capturedIpns=${this.pendingBuffer.capturedIpnsName?.slice(0, 20) ?? "none"}`);
|
|
4427
4542
|
if (this.flushTimer) {
|
|
4428
4543
|
clearTimeout(this.flushTimer);
|
|
4429
4544
|
this.flushTimer = null;
|
|
@@ -4456,6 +4571,8 @@ var IpfsStorageProvider = class {
|
|
|
4456
4571
|
return { success: false, error: "Not initialized", timestamp: Date.now() };
|
|
4457
4572
|
}
|
|
4458
4573
|
this.pendingBuffer.txfData = data;
|
|
4574
|
+
this.pendingBuffer.capturedIpnsKeyPair = this.ipnsKeyPair;
|
|
4575
|
+
this.pendingBuffer.capturedIpnsName = this.ipnsName;
|
|
4459
4576
|
this.scheduleFlush();
|
|
4460
4577
|
return { success: true, timestamp: Date.now() };
|
|
4461
4578
|
}
|
|
@@ -4466,8 +4583,12 @@ var IpfsStorageProvider = class {
|
|
|
4466
4583
|
* Perform the actual upload + IPNS publish synchronously.
|
|
4467
4584
|
* Called by executeFlush() and sync() — never by public save().
|
|
4468
4585
|
*/
|
|
4469
|
-
async _doSave(data) {
|
|
4470
|
-
|
|
4586
|
+
async _doSave(data, overrideIpns) {
|
|
4587
|
+
const ipnsKeyPair = overrideIpns?.keyPair ?? this.ipnsKeyPair;
|
|
4588
|
+
const ipnsName = overrideIpns?.name ?? this.ipnsName;
|
|
4589
|
+
const metaAddr = data?._meta?.address;
|
|
4590
|
+
logger.debug("IPFS-Storage", `_doSave: ipnsName=${ipnsName?.slice(0, 20)}..., override=${!!overrideIpns}, meta.address=${metaAddr?.slice(0, 20) ?? "none"}`);
|
|
4591
|
+
if (!ipnsKeyPair || !ipnsName) {
|
|
4471
4592
|
return { success: false, error: "Not initialized", timestamp: Date.now() };
|
|
4472
4593
|
}
|
|
4473
4594
|
this.emitEvent({ type: "storage:saving", timestamp: Date.now() });
|
|
@@ -4476,7 +4597,7 @@ var IpfsStorageProvider = class {
|
|
|
4476
4597
|
const metaUpdate = {
|
|
4477
4598
|
...data._meta,
|
|
4478
4599
|
version: this.dataVersion,
|
|
4479
|
-
ipnsName
|
|
4600
|
+
ipnsName,
|
|
4480
4601
|
updatedAt: Date.now()
|
|
4481
4602
|
};
|
|
4482
4603
|
if (this.remoteCid) {
|
|
@@ -4488,13 +4609,13 @@ var IpfsStorageProvider = class {
|
|
|
4488
4609
|
const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
|
|
4489
4610
|
const newSeq = baseSeq + 1n;
|
|
4490
4611
|
const marshalledRecord = await createSignedRecord(
|
|
4491
|
-
|
|
4612
|
+
ipnsKeyPair,
|
|
4492
4613
|
cid,
|
|
4493
4614
|
newSeq,
|
|
4494
4615
|
this.ipnsLifetimeMs
|
|
4495
4616
|
);
|
|
4496
4617
|
const publishResult = await this.httpClient.publishIpns(
|
|
4497
|
-
|
|
4618
|
+
ipnsName,
|
|
4498
4619
|
marshalledRecord
|
|
4499
4620
|
);
|
|
4500
4621
|
if (!publishResult.success) {
|
|
@@ -4509,14 +4630,14 @@ var IpfsStorageProvider = class {
|
|
|
4509
4630
|
this.ipnsSequenceNumber = newSeq;
|
|
4510
4631
|
this.lastCid = cid;
|
|
4511
4632
|
this.remoteCid = cid;
|
|
4512
|
-
this.cache.setIpnsRecord(
|
|
4633
|
+
this.cache.setIpnsRecord(ipnsName, {
|
|
4513
4634
|
cid,
|
|
4514
4635
|
sequence: newSeq,
|
|
4515
4636
|
gateway: "local"
|
|
4516
4637
|
});
|
|
4517
4638
|
this.cache.setContent(cid, updatedData);
|
|
4518
|
-
this.cache.markIpnsFresh(
|
|
4519
|
-
await this.statePersistence.save(
|
|
4639
|
+
this.cache.markIpnsFresh(ipnsName);
|
|
4640
|
+
await this.statePersistence.save(ipnsName, {
|
|
4520
4641
|
sequenceNumber: newSeq.toString(),
|
|
4521
4642
|
lastCid: cid,
|
|
4522
4643
|
version: this.dataVersion
|
|
@@ -4568,7 +4689,8 @@ var IpfsStorageProvider = class {
|
|
|
4568
4689
|
const baseData = active.txfData ?? {
|
|
4569
4690
|
_meta: { version: 0, address: this.identity?.directAddress ?? "", formatVersion: "2.0", updatedAt: 0 }
|
|
4570
4691
|
};
|
|
4571
|
-
const
|
|
4692
|
+
const overrideIpns = active.capturedIpnsKeyPair && active.capturedIpnsName ? { keyPair: active.capturedIpnsKeyPair, name: active.capturedIpnsName } : void 0;
|
|
4693
|
+
const result = await this._doSave(baseData, overrideIpns);
|
|
4572
4694
|
if (!result.success) {
|
|
4573
4695
|
throw new SphereError(result.error ?? "Save failed", "STORAGE_ERROR");
|
|
4574
4696
|
}
|
|
@@ -4871,6 +4993,13 @@ var IpfsStorageProvider = class {
|
|
|
4871
4993
|
log(message) {
|
|
4872
4994
|
logger.debug("IPFS-Storage", message);
|
|
4873
4995
|
}
|
|
4996
|
+
/**
|
|
4997
|
+
* Create an independent instance for a different address.
|
|
4998
|
+
* Shares the same gateway/timeout config but has fresh IPNS state.
|
|
4999
|
+
*/
|
|
5000
|
+
createForAddress() {
|
|
5001
|
+
return new _IpfsStorageProvider(this._config, this._statePersistenceCtor);
|
|
5002
|
+
}
|
|
4874
5003
|
};
|
|
4875
5004
|
|
|
4876
5005
|
// impl/nodejs/ipfs/nodejs-ipfs-state-persistence.ts
|