@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
|
@@ -758,7 +758,7 @@ var STORE_TOKENS = "tokens";
|
|
|
758
758
|
var STORE_META = "meta";
|
|
759
759
|
var STORE_HISTORY = "history";
|
|
760
760
|
var connectionSeq2 = 0;
|
|
761
|
-
var IndexedDBTokenStorageProvider = class {
|
|
761
|
+
var IndexedDBTokenStorageProvider = class _IndexedDBTokenStorageProvider {
|
|
762
762
|
id = "indexeddb-token-storage";
|
|
763
763
|
name = "IndexedDB Token Storage";
|
|
764
764
|
type = "local";
|
|
@@ -1206,6 +1206,16 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
1206
1206
|
db.close();
|
|
1207
1207
|
}
|
|
1208
1208
|
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Create an independent instance for a different address.
|
|
1211
|
+
* The new instance shares the same config but has its own IDB connection.
|
|
1212
|
+
*/
|
|
1213
|
+
createForAddress() {
|
|
1214
|
+
return new _IndexedDBTokenStorageProvider({
|
|
1215
|
+
dbNamePrefix: this.dbNamePrefix,
|
|
1216
|
+
debug: this.debug
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1209
1219
|
};
|
|
1210
1220
|
function createIndexedDBTokenStorageProvider(config) {
|
|
1211
1221
|
return new IndexedDBTokenStorageProvider(config);
|
|
@@ -1634,6 +1644,8 @@ var NostrTransportProvider = class {
|
|
|
1634
1644
|
storage = null;
|
|
1635
1645
|
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
1636
1646
|
lastEventTs = 0;
|
|
1647
|
+
/** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
|
|
1648
|
+
fallbackSince = null;
|
|
1637
1649
|
identity = null;
|
|
1638
1650
|
keyManager = null;
|
|
1639
1651
|
status = "disconnected";
|
|
@@ -1666,6 +1678,48 @@ var NostrTransportProvider = class {
|
|
|
1666
1678
|
};
|
|
1667
1679
|
this.storage = config.storage ?? null;
|
|
1668
1680
|
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Get the WebSocket factory (used by MultiAddressTransportMux to share the same factory).
|
|
1683
|
+
*/
|
|
1684
|
+
getWebSocketFactory() {
|
|
1685
|
+
return this.config.createWebSocket;
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Get the configured relay URLs.
|
|
1689
|
+
*/
|
|
1690
|
+
getConfiguredRelays() {
|
|
1691
|
+
return [...this.config.relays];
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* Get the storage adapter.
|
|
1695
|
+
*/
|
|
1696
|
+
getStorageAdapter() {
|
|
1697
|
+
return this.storage;
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Suppress event subscriptions — unsubscribe wallet/chat filters
|
|
1701
|
+
* but keep the connection alive for resolve/identity-binding operations.
|
|
1702
|
+
* Used when MultiAddressTransportMux takes over event handling.
|
|
1703
|
+
*/
|
|
1704
|
+
suppressSubscriptions() {
|
|
1705
|
+
if (!this.nostrClient) return;
|
|
1706
|
+
if (this.walletSubscriptionId) {
|
|
1707
|
+
this.nostrClient.unsubscribe(this.walletSubscriptionId);
|
|
1708
|
+
this.walletSubscriptionId = null;
|
|
1709
|
+
}
|
|
1710
|
+
if (this.chatSubscriptionId) {
|
|
1711
|
+
this.nostrClient.unsubscribe(this.chatSubscriptionId);
|
|
1712
|
+
this.chatSubscriptionId = null;
|
|
1713
|
+
}
|
|
1714
|
+
if (this.mainSubscriptionId) {
|
|
1715
|
+
this.nostrClient.unsubscribe(this.mainSubscriptionId);
|
|
1716
|
+
this.mainSubscriptionId = null;
|
|
1717
|
+
}
|
|
1718
|
+
this._subscriptionsSuppressed = true;
|
|
1719
|
+
logger.debug("Nostr", "Subscriptions suppressed \u2014 mux handles event routing");
|
|
1720
|
+
}
|
|
1721
|
+
// Flag to prevent re-subscription after suppressSubscriptions()
|
|
1722
|
+
_subscriptionsSuppressed = false;
|
|
1669
1723
|
// ===========================================================================
|
|
1670
1724
|
// BaseProvider Implementation
|
|
1671
1725
|
// ===========================================================================
|
|
@@ -1733,6 +1787,7 @@ var NostrTransportProvider = class {
|
|
|
1733
1787
|
this.mainSubscriptionId = null;
|
|
1734
1788
|
this.walletSubscriptionId = null;
|
|
1735
1789
|
this.chatSubscriptionId = null;
|
|
1790
|
+
this.chatEoseFired = false;
|
|
1736
1791
|
this.status = "disconnected";
|
|
1737
1792
|
this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
|
|
1738
1793
|
logger.debug("Nostr", "Disconnected from all relays");
|
|
@@ -1843,6 +1898,8 @@ var NostrTransportProvider = class {
|
|
|
1843
1898
|
// ===========================================================================
|
|
1844
1899
|
async setIdentity(identity) {
|
|
1845
1900
|
this.identity = identity;
|
|
1901
|
+
this.processedEventIds.clear();
|
|
1902
|
+
this.lastEventTs = 0;
|
|
1846
1903
|
const secretKey = Buffer2.from(identity.privateKey, "hex");
|
|
1847
1904
|
this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
|
|
1848
1905
|
const nostrPubkey = this.keyManager.getPublicKeyHex();
|
|
@@ -1885,6 +1942,9 @@ var NostrTransportProvider = class {
|
|
|
1885
1942
|
await this.subscribeToEvents();
|
|
1886
1943
|
}
|
|
1887
1944
|
}
|
|
1945
|
+
setFallbackSince(sinceSeconds) {
|
|
1946
|
+
this.fallbackSince = sinceSeconds;
|
|
1947
|
+
}
|
|
1888
1948
|
/**
|
|
1889
1949
|
* Get the Nostr-format public key (32 bytes / 64 hex chars)
|
|
1890
1950
|
* This is the x-coordinate only, without the 02/03 prefix.
|
|
@@ -2056,6 +2116,20 @@ var NostrTransportProvider = class {
|
|
|
2056
2116
|
this.typingIndicatorHandlers.add(handler);
|
|
2057
2117
|
return () => this.typingIndicatorHandlers.delete(handler);
|
|
2058
2118
|
}
|
|
2119
|
+
onChatReady(handler) {
|
|
2120
|
+
if (this.chatEoseFired) {
|
|
2121
|
+
try {
|
|
2122
|
+
handler();
|
|
2123
|
+
} catch {
|
|
2124
|
+
}
|
|
2125
|
+
return () => {
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
this.chatEoseHandlers.push(handler);
|
|
2129
|
+
return () => {
|
|
2130
|
+
this.chatEoseHandlers = this.chatEoseHandlers.filter((h) => h !== handler);
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2059
2133
|
// ===========================================================================
|
|
2060
2134
|
// Composing Indicators (NIP-59 kind 25050)
|
|
2061
2135
|
// ===========================================================================
|
|
@@ -2773,8 +2847,15 @@ var NostrTransportProvider = class {
|
|
|
2773
2847
|
// Track subscription IDs for cleanup
|
|
2774
2848
|
walletSubscriptionId = null;
|
|
2775
2849
|
chatSubscriptionId = null;
|
|
2850
|
+
// Chat EOSE handlers — fired once when relay finishes delivering stored DMs
|
|
2851
|
+
chatEoseHandlers = [];
|
|
2852
|
+
chatEoseFired = false;
|
|
2776
2853
|
async subscribeToEvents() {
|
|
2777
2854
|
logger.debug("Nostr", "subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
|
|
2855
|
+
if (this._subscriptionsSuppressed) {
|
|
2856
|
+
logger.debug("Nostr", "subscribeToEvents: suppressed \u2014 mux handles event routing");
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2778
2859
|
if (!this.identity || !this.keyManager || !this.nostrClient) {
|
|
2779
2860
|
logger.debug("Nostr", "subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
|
|
2780
2861
|
return;
|
|
@@ -2801,7 +2882,13 @@ var NostrTransportProvider = class {
|
|
|
2801
2882
|
if (stored) {
|
|
2802
2883
|
since = parseInt(stored, 10);
|
|
2803
2884
|
this.lastEventTs = since;
|
|
2885
|
+
this.fallbackSince = null;
|
|
2804
2886
|
logger.debug("Nostr", "Resuming from stored event timestamp:", since);
|
|
2887
|
+
} else if (this.fallbackSince !== null) {
|
|
2888
|
+
since = this.fallbackSince;
|
|
2889
|
+
this.lastEventTs = since;
|
|
2890
|
+
this.fallbackSince = null;
|
|
2891
|
+
logger.debug("Nostr", "Using fallback since timestamp:", since);
|
|
2805
2892
|
} else {
|
|
2806
2893
|
since = Math.floor(Date.now() / 1e3);
|
|
2807
2894
|
logger.debug("Nostr", "No stored timestamp, starting from now:", since);
|
|
@@ -2809,6 +2896,7 @@ var NostrTransportProvider = class {
|
|
|
2809
2896
|
} catch (err) {
|
|
2810
2897
|
logger.debug("Nostr", "Failed to read last event timestamp, falling back to now:", err);
|
|
2811
2898
|
since = Math.floor(Date.now() / 1e3);
|
|
2899
|
+
this.fallbackSince = null;
|
|
2812
2900
|
}
|
|
2813
2901
|
} else {
|
|
2814
2902
|
since = Math.floor(Date.now() / 1e3) - 86400;
|
|
@@ -2862,6 +2950,16 @@ var NostrTransportProvider = class {
|
|
|
2862
2950
|
},
|
|
2863
2951
|
onEndOfStoredEvents: () => {
|
|
2864
2952
|
logger.debug("Nostr", "Chat subscription ready (EOSE)");
|
|
2953
|
+
if (!this.chatEoseFired) {
|
|
2954
|
+
this.chatEoseFired = true;
|
|
2955
|
+
for (const handler of this.chatEoseHandlers) {
|
|
2956
|
+
try {
|
|
2957
|
+
handler();
|
|
2958
|
+
} catch {
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
this.chatEoseHandlers = [];
|
|
2962
|
+
}
|
|
2865
2963
|
},
|
|
2866
2964
|
onError: (_subId, error) => {
|
|
2867
2965
|
logger.debug("Nostr", "Chat subscription error:", error);
|
|
@@ -4819,11 +4917,17 @@ var AsyncSerialQueue = class {
|
|
|
4819
4917
|
var WriteBuffer = class {
|
|
4820
4918
|
/** Full TXF data from save() calls — latest wins */
|
|
4821
4919
|
txfData = null;
|
|
4920
|
+
/** IPNS context captured at save() time — ensures flush writes to the correct
|
|
4921
|
+
* IPNS record even if identity changes between save() and flush(). */
|
|
4922
|
+
capturedIpnsKeyPair = null;
|
|
4923
|
+
capturedIpnsName = null;
|
|
4822
4924
|
get isEmpty() {
|
|
4823
4925
|
return this.txfData === null;
|
|
4824
4926
|
}
|
|
4825
4927
|
clear() {
|
|
4826
4928
|
this.txfData = null;
|
|
4929
|
+
this.capturedIpnsKeyPair = null;
|
|
4930
|
+
this.capturedIpnsName = null;
|
|
4827
4931
|
}
|
|
4828
4932
|
/**
|
|
4829
4933
|
* Merge another buffer's contents into this one (for rollback).
|
|
@@ -4832,12 +4936,14 @@ var WriteBuffer = class {
|
|
|
4832
4936
|
mergeFrom(other) {
|
|
4833
4937
|
if (other.txfData && !this.txfData) {
|
|
4834
4938
|
this.txfData = other.txfData;
|
|
4939
|
+
this.capturedIpnsKeyPair = other.capturedIpnsKeyPair;
|
|
4940
|
+
this.capturedIpnsName = other.capturedIpnsName;
|
|
4835
4941
|
}
|
|
4836
4942
|
}
|
|
4837
4943
|
};
|
|
4838
4944
|
|
|
4839
4945
|
// impl/shared/ipfs/ipfs-storage-provider.ts
|
|
4840
|
-
var IpfsStorageProvider = class {
|
|
4946
|
+
var IpfsStorageProvider = class _IpfsStorageProvider {
|
|
4841
4947
|
id = "ipfs";
|
|
4842
4948
|
name = "IPFS Storage";
|
|
4843
4949
|
type = "p2p";
|
|
@@ -4882,7 +4988,12 @@ var IpfsStorageProvider = class {
|
|
|
4882
4988
|
flushDebounceMs;
|
|
4883
4989
|
/** Set to true during shutdown to prevent new flushes */
|
|
4884
4990
|
isShuttingDown = false;
|
|
4991
|
+
/** Stored config for createForAddress() cloning */
|
|
4992
|
+
_config;
|
|
4993
|
+
_statePersistenceCtor;
|
|
4885
4994
|
constructor(config, statePersistence) {
|
|
4995
|
+
this._config = config;
|
|
4996
|
+
this._statePersistenceCtor = statePersistence;
|
|
4886
4997
|
const gateways = config?.gateways ?? getIpfsGatewayUrls();
|
|
4887
4998
|
this.debug = config?.debug ?? false;
|
|
4888
4999
|
this.ipnsLifetimeMs = config?.ipnsLifetimeMs ?? 99 * 365 * 24 * 60 * 60 * 1e3;
|
|
@@ -5002,6 +5113,7 @@ var IpfsStorageProvider = class {
|
|
|
5002
5113
|
}
|
|
5003
5114
|
async shutdown() {
|
|
5004
5115
|
this.isShuttingDown = true;
|
|
5116
|
+
logger.debug("IPFS-Storage", `shutdown: ipnsName=${this.ipnsName?.slice(0, 20)}..., pendingEmpty=${this.pendingBuffer.isEmpty}, capturedIpns=${this.pendingBuffer.capturedIpnsName?.slice(0, 20) ?? "none"}`);
|
|
5005
5117
|
if (this.flushTimer) {
|
|
5006
5118
|
clearTimeout(this.flushTimer);
|
|
5007
5119
|
this.flushTimer = null;
|
|
@@ -5034,6 +5146,8 @@ var IpfsStorageProvider = class {
|
|
|
5034
5146
|
return { success: false, error: "Not initialized", timestamp: Date.now() };
|
|
5035
5147
|
}
|
|
5036
5148
|
this.pendingBuffer.txfData = data;
|
|
5149
|
+
this.pendingBuffer.capturedIpnsKeyPair = this.ipnsKeyPair;
|
|
5150
|
+
this.pendingBuffer.capturedIpnsName = this.ipnsName;
|
|
5037
5151
|
this.scheduleFlush();
|
|
5038
5152
|
return { success: true, timestamp: Date.now() };
|
|
5039
5153
|
}
|
|
@@ -5044,8 +5158,12 @@ var IpfsStorageProvider = class {
|
|
|
5044
5158
|
* Perform the actual upload + IPNS publish synchronously.
|
|
5045
5159
|
* Called by executeFlush() and sync() — never by public save().
|
|
5046
5160
|
*/
|
|
5047
|
-
async _doSave(data) {
|
|
5048
|
-
|
|
5161
|
+
async _doSave(data, overrideIpns) {
|
|
5162
|
+
const ipnsKeyPair = overrideIpns?.keyPair ?? this.ipnsKeyPair;
|
|
5163
|
+
const ipnsName = overrideIpns?.name ?? this.ipnsName;
|
|
5164
|
+
const metaAddr = data?._meta?.address;
|
|
5165
|
+
logger.debug("IPFS-Storage", `_doSave: ipnsName=${ipnsName?.slice(0, 20)}..., override=${!!overrideIpns}, meta.address=${metaAddr?.slice(0, 20) ?? "none"}`);
|
|
5166
|
+
if (!ipnsKeyPair || !ipnsName) {
|
|
5049
5167
|
return { success: false, error: "Not initialized", timestamp: Date.now() };
|
|
5050
5168
|
}
|
|
5051
5169
|
this.emitEvent({ type: "storage:saving", timestamp: Date.now() });
|
|
@@ -5054,7 +5172,7 @@ var IpfsStorageProvider = class {
|
|
|
5054
5172
|
const metaUpdate = {
|
|
5055
5173
|
...data._meta,
|
|
5056
5174
|
version: this.dataVersion,
|
|
5057
|
-
ipnsName
|
|
5175
|
+
ipnsName,
|
|
5058
5176
|
updatedAt: Date.now()
|
|
5059
5177
|
};
|
|
5060
5178
|
if (this.remoteCid) {
|
|
@@ -5066,13 +5184,13 @@ var IpfsStorageProvider = class {
|
|
|
5066
5184
|
const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
|
|
5067
5185
|
const newSeq = baseSeq + 1n;
|
|
5068
5186
|
const marshalledRecord = await createSignedRecord(
|
|
5069
|
-
|
|
5187
|
+
ipnsKeyPair,
|
|
5070
5188
|
cid,
|
|
5071
5189
|
newSeq,
|
|
5072
5190
|
this.ipnsLifetimeMs
|
|
5073
5191
|
);
|
|
5074
5192
|
const publishResult = await this.httpClient.publishIpns(
|
|
5075
|
-
|
|
5193
|
+
ipnsName,
|
|
5076
5194
|
marshalledRecord
|
|
5077
5195
|
);
|
|
5078
5196
|
if (!publishResult.success) {
|
|
@@ -5087,14 +5205,14 @@ var IpfsStorageProvider = class {
|
|
|
5087
5205
|
this.ipnsSequenceNumber = newSeq;
|
|
5088
5206
|
this.lastCid = cid;
|
|
5089
5207
|
this.remoteCid = cid;
|
|
5090
|
-
this.cache.setIpnsRecord(
|
|
5208
|
+
this.cache.setIpnsRecord(ipnsName, {
|
|
5091
5209
|
cid,
|
|
5092
5210
|
sequence: newSeq,
|
|
5093
5211
|
gateway: "local"
|
|
5094
5212
|
});
|
|
5095
5213
|
this.cache.setContent(cid, updatedData);
|
|
5096
|
-
this.cache.markIpnsFresh(
|
|
5097
|
-
await this.statePersistence.save(
|
|
5214
|
+
this.cache.markIpnsFresh(ipnsName);
|
|
5215
|
+
await this.statePersistence.save(ipnsName, {
|
|
5098
5216
|
sequenceNumber: newSeq.toString(),
|
|
5099
5217
|
lastCid: cid,
|
|
5100
5218
|
version: this.dataVersion
|
|
@@ -5146,7 +5264,8 @@ var IpfsStorageProvider = class {
|
|
|
5146
5264
|
const baseData = active.txfData ?? {
|
|
5147
5265
|
_meta: { version: 0, address: this.identity?.directAddress ?? "", formatVersion: "2.0", updatedAt: 0 }
|
|
5148
5266
|
};
|
|
5149
|
-
const
|
|
5267
|
+
const overrideIpns = active.capturedIpnsKeyPair && active.capturedIpnsName ? { keyPair: active.capturedIpnsKeyPair, name: active.capturedIpnsName } : void 0;
|
|
5268
|
+
const result = await this._doSave(baseData, overrideIpns);
|
|
5150
5269
|
if (!result.success) {
|
|
5151
5270
|
throw new SphereError(result.error ?? "Save failed", "STORAGE_ERROR");
|
|
5152
5271
|
}
|
|
@@ -5449,6 +5568,13 @@ var IpfsStorageProvider = class {
|
|
|
5449
5568
|
log(message) {
|
|
5450
5569
|
logger.debug("IPFS-Storage", message);
|
|
5451
5570
|
}
|
|
5571
|
+
/**
|
|
5572
|
+
* Create an independent instance for a different address.
|
|
5573
|
+
* Shares the same gateway/timeout config but has fresh IPNS state.
|
|
5574
|
+
*/
|
|
5575
|
+
createForAddress() {
|
|
5576
|
+
return new _IpfsStorageProvider(this._config, this._statePersistenceCtor);
|
|
5577
|
+
}
|
|
5452
5578
|
};
|
|
5453
5579
|
|
|
5454
5580
|
// impl/browser/ipfs/browser-ipfs-state-persistence.ts
|