@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
|
@@ -818,7 +818,7 @@ var STORE_TOKENS = "tokens";
|
|
|
818
818
|
var STORE_META = "meta";
|
|
819
819
|
var STORE_HISTORY = "history";
|
|
820
820
|
var connectionSeq2 = 0;
|
|
821
|
-
var IndexedDBTokenStorageProvider = class {
|
|
821
|
+
var IndexedDBTokenStorageProvider = class _IndexedDBTokenStorageProvider {
|
|
822
822
|
id = "indexeddb-token-storage";
|
|
823
823
|
name = "IndexedDB Token Storage";
|
|
824
824
|
type = "local";
|
|
@@ -1266,6 +1266,16 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
1266
1266
|
db.close();
|
|
1267
1267
|
}
|
|
1268
1268
|
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Create an independent instance for a different address.
|
|
1271
|
+
* The new instance shares the same config but has its own IDB connection.
|
|
1272
|
+
*/
|
|
1273
|
+
createForAddress() {
|
|
1274
|
+
return new _IndexedDBTokenStorageProvider({
|
|
1275
|
+
dbNamePrefix: this.dbNamePrefix,
|
|
1276
|
+
debug: this.debug
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1269
1279
|
};
|
|
1270
1280
|
function createIndexedDBTokenStorageProvider(config) {
|
|
1271
1281
|
return new IndexedDBTokenStorageProvider(config);
|
|
@@ -1682,6 +1692,8 @@ var NostrTransportProvider = class {
|
|
|
1682
1692
|
storage = null;
|
|
1683
1693
|
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
1684
1694
|
lastEventTs = 0;
|
|
1695
|
+
/** Fallback 'since' timestamp for first-time address subscriptions (consumed once). */
|
|
1696
|
+
fallbackSince = null;
|
|
1685
1697
|
identity = null;
|
|
1686
1698
|
keyManager = null;
|
|
1687
1699
|
status = "disconnected";
|
|
@@ -1714,6 +1726,48 @@ var NostrTransportProvider = class {
|
|
|
1714
1726
|
};
|
|
1715
1727
|
this.storage = config.storage ?? null;
|
|
1716
1728
|
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Get the WebSocket factory (used by MultiAddressTransportMux to share the same factory).
|
|
1731
|
+
*/
|
|
1732
|
+
getWebSocketFactory() {
|
|
1733
|
+
return this.config.createWebSocket;
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Get the configured relay URLs.
|
|
1737
|
+
*/
|
|
1738
|
+
getConfiguredRelays() {
|
|
1739
|
+
return [...this.config.relays];
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* Get the storage adapter.
|
|
1743
|
+
*/
|
|
1744
|
+
getStorageAdapter() {
|
|
1745
|
+
return this.storage;
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Suppress event subscriptions — unsubscribe wallet/chat filters
|
|
1749
|
+
* but keep the connection alive for resolve/identity-binding operations.
|
|
1750
|
+
* Used when MultiAddressTransportMux takes over event handling.
|
|
1751
|
+
*/
|
|
1752
|
+
suppressSubscriptions() {
|
|
1753
|
+
if (!this.nostrClient) return;
|
|
1754
|
+
if (this.walletSubscriptionId) {
|
|
1755
|
+
this.nostrClient.unsubscribe(this.walletSubscriptionId);
|
|
1756
|
+
this.walletSubscriptionId = null;
|
|
1757
|
+
}
|
|
1758
|
+
if (this.chatSubscriptionId) {
|
|
1759
|
+
this.nostrClient.unsubscribe(this.chatSubscriptionId);
|
|
1760
|
+
this.chatSubscriptionId = null;
|
|
1761
|
+
}
|
|
1762
|
+
if (this.mainSubscriptionId) {
|
|
1763
|
+
this.nostrClient.unsubscribe(this.mainSubscriptionId);
|
|
1764
|
+
this.mainSubscriptionId = null;
|
|
1765
|
+
}
|
|
1766
|
+
this._subscriptionsSuppressed = true;
|
|
1767
|
+
logger.debug("Nostr", "Subscriptions suppressed \u2014 mux handles event routing");
|
|
1768
|
+
}
|
|
1769
|
+
// Flag to prevent re-subscription after suppressSubscriptions()
|
|
1770
|
+
_subscriptionsSuppressed = false;
|
|
1717
1771
|
// ===========================================================================
|
|
1718
1772
|
// BaseProvider Implementation
|
|
1719
1773
|
// ===========================================================================
|
|
@@ -1781,6 +1835,7 @@ var NostrTransportProvider = class {
|
|
|
1781
1835
|
this.mainSubscriptionId = null;
|
|
1782
1836
|
this.walletSubscriptionId = null;
|
|
1783
1837
|
this.chatSubscriptionId = null;
|
|
1838
|
+
this.chatEoseFired = false;
|
|
1784
1839
|
this.status = "disconnected";
|
|
1785
1840
|
this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
|
|
1786
1841
|
logger.debug("Nostr", "Disconnected from all relays");
|
|
@@ -1891,6 +1946,8 @@ var NostrTransportProvider = class {
|
|
|
1891
1946
|
// ===========================================================================
|
|
1892
1947
|
async setIdentity(identity) {
|
|
1893
1948
|
this.identity = identity;
|
|
1949
|
+
this.processedEventIds.clear();
|
|
1950
|
+
this.lastEventTs = 0;
|
|
1894
1951
|
const secretKey = import_buffer.Buffer.from(identity.privateKey, "hex");
|
|
1895
1952
|
this.keyManager = import_nostr_js_sdk.NostrKeyManager.fromPrivateKey(secretKey);
|
|
1896
1953
|
const nostrPubkey = this.keyManager.getPublicKeyHex();
|
|
@@ -1933,6 +1990,9 @@ var NostrTransportProvider = class {
|
|
|
1933
1990
|
await this.subscribeToEvents();
|
|
1934
1991
|
}
|
|
1935
1992
|
}
|
|
1993
|
+
setFallbackSince(sinceSeconds) {
|
|
1994
|
+
this.fallbackSince = sinceSeconds;
|
|
1995
|
+
}
|
|
1936
1996
|
/**
|
|
1937
1997
|
* Get the Nostr-format public key (32 bytes / 64 hex chars)
|
|
1938
1998
|
* This is the x-coordinate only, without the 02/03 prefix.
|
|
@@ -2104,6 +2164,20 @@ var NostrTransportProvider = class {
|
|
|
2104
2164
|
this.typingIndicatorHandlers.add(handler);
|
|
2105
2165
|
return () => this.typingIndicatorHandlers.delete(handler);
|
|
2106
2166
|
}
|
|
2167
|
+
onChatReady(handler) {
|
|
2168
|
+
if (this.chatEoseFired) {
|
|
2169
|
+
try {
|
|
2170
|
+
handler();
|
|
2171
|
+
} catch {
|
|
2172
|
+
}
|
|
2173
|
+
return () => {
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
this.chatEoseHandlers.push(handler);
|
|
2177
|
+
return () => {
|
|
2178
|
+
this.chatEoseHandlers = this.chatEoseHandlers.filter((h) => h !== handler);
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2107
2181
|
// ===========================================================================
|
|
2108
2182
|
// Composing Indicators (NIP-59 kind 25050)
|
|
2109
2183
|
// ===========================================================================
|
|
@@ -2821,8 +2895,15 @@ var NostrTransportProvider = class {
|
|
|
2821
2895
|
// Track subscription IDs for cleanup
|
|
2822
2896
|
walletSubscriptionId = null;
|
|
2823
2897
|
chatSubscriptionId = null;
|
|
2898
|
+
// Chat EOSE handlers — fired once when relay finishes delivering stored DMs
|
|
2899
|
+
chatEoseHandlers = [];
|
|
2900
|
+
chatEoseFired = false;
|
|
2824
2901
|
async subscribeToEvents() {
|
|
2825
2902
|
logger.debug("Nostr", "subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
|
|
2903
|
+
if (this._subscriptionsSuppressed) {
|
|
2904
|
+
logger.debug("Nostr", "subscribeToEvents: suppressed \u2014 mux handles event routing");
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2826
2907
|
if (!this.identity || !this.keyManager || !this.nostrClient) {
|
|
2827
2908
|
logger.debug("Nostr", "subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
|
|
2828
2909
|
return;
|
|
@@ -2849,7 +2930,13 @@ var NostrTransportProvider = class {
|
|
|
2849
2930
|
if (stored) {
|
|
2850
2931
|
since = parseInt(stored, 10);
|
|
2851
2932
|
this.lastEventTs = since;
|
|
2933
|
+
this.fallbackSince = null;
|
|
2852
2934
|
logger.debug("Nostr", "Resuming from stored event timestamp:", since);
|
|
2935
|
+
} else if (this.fallbackSince !== null) {
|
|
2936
|
+
since = this.fallbackSince;
|
|
2937
|
+
this.lastEventTs = since;
|
|
2938
|
+
this.fallbackSince = null;
|
|
2939
|
+
logger.debug("Nostr", "Using fallback since timestamp:", since);
|
|
2853
2940
|
} else {
|
|
2854
2941
|
since = Math.floor(Date.now() / 1e3);
|
|
2855
2942
|
logger.debug("Nostr", "No stored timestamp, starting from now:", since);
|
|
@@ -2857,6 +2944,7 @@ var NostrTransportProvider = class {
|
|
|
2857
2944
|
} catch (err) {
|
|
2858
2945
|
logger.debug("Nostr", "Failed to read last event timestamp, falling back to now:", err);
|
|
2859
2946
|
since = Math.floor(Date.now() / 1e3);
|
|
2947
|
+
this.fallbackSince = null;
|
|
2860
2948
|
}
|
|
2861
2949
|
} else {
|
|
2862
2950
|
since = Math.floor(Date.now() / 1e3) - 86400;
|
|
@@ -2910,6 +2998,16 @@ var NostrTransportProvider = class {
|
|
|
2910
2998
|
},
|
|
2911
2999
|
onEndOfStoredEvents: () => {
|
|
2912
3000
|
logger.debug("Nostr", "Chat subscription ready (EOSE)");
|
|
3001
|
+
if (!this.chatEoseFired) {
|
|
3002
|
+
this.chatEoseFired = true;
|
|
3003
|
+
for (const handler of this.chatEoseHandlers) {
|
|
3004
|
+
try {
|
|
3005
|
+
handler();
|
|
3006
|
+
} catch {
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
this.chatEoseHandlers = [];
|
|
3010
|
+
}
|
|
2913
3011
|
},
|
|
2914
3012
|
onError: (_subId, error) => {
|
|
2915
3013
|
logger.debug("Nostr", "Chat subscription error:", error);
|
|
@@ -4867,11 +4965,17 @@ var AsyncSerialQueue = class {
|
|
|
4867
4965
|
var WriteBuffer = class {
|
|
4868
4966
|
/** Full TXF data from save() calls — latest wins */
|
|
4869
4967
|
txfData = null;
|
|
4968
|
+
/** IPNS context captured at save() time — ensures flush writes to the correct
|
|
4969
|
+
* IPNS record even if identity changes between save() and flush(). */
|
|
4970
|
+
capturedIpnsKeyPair = null;
|
|
4971
|
+
capturedIpnsName = null;
|
|
4870
4972
|
get isEmpty() {
|
|
4871
4973
|
return this.txfData === null;
|
|
4872
4974
|
}
|
|
4873
4975
|
clear() {
|
|
4874
4976
|
this.txfData = null;
|
|
4977
|
+
this.capturedIpnsKeyPair = null;
|
|
4978
|
+
this.capturedIpnsName = null;
|
|
4875
4979
|
}
|
|
4876
4980
|
/**
|
|
4877
4981
|
* Merge another buffer's contents into this one (for rollback).
|
|
@@ -4880,12 +4984,14 @@ var WriteBuffer = class {
|
|
|
4880
4984
|
mergeFrom(other) {
|
|
4881
4985
|
if (other.txfData && !this.txfData) {
|
|
4882
4986
|
this.txfData = other.txfData;
|
|
4987
|
+
this.capturedIpnsKeyPair = other.capturedIpnsKeyPair;
|
|
4988
|
+
this.capturedIpnsName = other.capturedIpnsName;
|
|
4883
4989
|
}
|
|
4884
4990
|
}
|
|
4885
4991
|
};
|
|
4886
4992
|
|
|
4887
4993
|
// impl/shared/ipfs/ipfs-storage-provider.ts
|
|
4888
|
-
var IpfsStorageProvider = class {
|
|
4994
|
+
var IpfsStorageProvider = class _IpfsStorageProvider {
|
|
4889
4995
|
id = "ipfs";
|
|
4890
4996
|
name = "IPFS Storage";
|
|
4891
4997
|
type = "p2p";
|
|
@@ -4930,7 +5036,12 @@ var IpfsStorageProvider = class {
|
|
|
4930
5036
|
flushDebounceMs;
|
|
4931
5037
|
/** Set to true during shutdown to prevent new flushes */
|
|
4932
5038
|
isShuttingDown = false;
|
|
5039
|
+
/** Stored config for createForAddress() cloning */
|
|
5040
|
+
_config;
|
|
5041
|
+
_statePersistenceCtor;
|
|
4933
5042
|
constructor(config, statePersistence) {
|
|
5043
|
+
this._config = config;
|
|
5044
|
+
this._statePersistenceCtor = statePersistence;
|
|
4934
5045
|
const gateways = config?.gateways ?? getIpfsGatewayUrls();
|
|
4935
5046
|
this.debug = config?.debug ?? false;
|
|
4936
5047
|
this.ipnsLifetimeMs = config?.ipnsLifetimeMs ?? 99 * 365 * 24 * 60 * 60 * 1e3;
|
|
@@ -5050,6 +5161,7 @@ var IpfsStorageProvider = class {
|
|
|
5050
5161
|
}
|
|
5051
5162
|
async shutdown() {
|
|
5052
5163
|
this.isShuttingDown = true;
|
|
5164
|
+
logger.debug("IPFS-Storage", `shutdown: ipnsName=${this.ipnsName?.slice(0, 20)}..., pendingEmpty=${this.pendingBuffer.isEmpty}, capturedIpns=${this.pendingBuffer.capturedIpnsName?.slice(0, 20) ?? "none"}`);
|
|
5053
5165
|
if (this.flushTimer) {
|
|
5054
5166
|
clearTimeout(this.flushTimer);
|
|
5055
5167
|
this.flushTimer = null;
|
|
@@ -5082,6 +5194,8 @@ var IpfsStorageProvider = class {
|
|
|
5082
5194
|
return { success: false, error: "Not initialized", timestamp: Date.now() };
|
|
5083
5195
|
}
|
|
5084
5196
|
this.pendingBuffer.txfData = data;
|
|
5197
|
+
this.pendingBuffer.capturedIpnsKeyPair = this.ipnsKeyPair;
|
|
5198
|
+
this.pendingBuffer.capturedIpnsName = this.ipnsName;
|
|
5085
5199
|
this.scheduleFlush();
|
|
5086
5200
|
return { success: true, timestamp: Date.now() };
|
|
5087
5201
|
}
|
|
@@ -5092,8 +5206,12 @@ var IpfsStorageProvider = class {
|
|
|
5092
5206
|
* Perform the actual upload + IPNS publish synchronously.
|
|
5093
5207
|
* Called by executeFlush() and sync() — never by public save().
|
|
5094
5208
|
*/
|
|
5095
|
-
async _doSave(data) {
|
|
5096
|
-
|
|
5209
|
+
async _doSave(data, overrideIpns) {
|
|
5210
|
+
const ipnsKeyPair = overrideIpns?.keyPair ?? this.ipnsKeyPair;
|
|
5211
|
+
const ipnsName = overrideIpns?.name ?? this.ipnsName;
|
|
5212
|
+
const metaAddr = data?._meta?.address;
|
|
5213
|
+
logger.debug("IPFS-Storage", `_doSave: ipnsName=${ipnsName?.slice(0, 20)}..., override=${!!overrideIpns}, meta.address=${metaAddr?.slice(0, 20) ?? "none"}`);
|
|
5214
|
+
if (!ipnsKeyPair || !ipnsName) {
|
|
5097
5215
|
return { success: false, error: "Not initialized", timestamp: Date.now() };
|
|
5098
5216
|
}
|
|
5099
5217
|
this.emitEvent({ type: "storage:saving", timestamp: Date.now() });
|
|
@@ -5102,7 +5220,7 @@ var IpfsStorageProvider = class {
|
|
|
5102
5220
|
const metaUpdate = {
|
|
5103
5221
|
...data._meta,
|
|
5104
5222
|
version: this.dataVersion,
|
|
5105
|
-
ipnsName
|
|
5223
|
+
ipnsName,
|
|
5106
5224
|
updatedAt: Date.now()
|
|
5107
5225
|
};
|
|
5108
5226
|
if (this.remoteCid) {
|
|
@@ -5114,13 +5232,13 @@ var IpfsStorageProvider = class {
|
|
|
5114
5232
|
const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
|
|
5115
5233
|
const newSeq = baseSeq + 1n;
|
|
5116
5234
|
const marshalledRecord = await createSignedRecord(
|
|
5117
|
-
|
|
5235
|
+
ipnsKeyPair,
|
|
5118
5236
|
cid,
|
|
5119
5237
|
newSeq,
|
|
5120
5238
|
this.ipnsLifetimeMs
|
|
5121
5239
|
);
|
|
5122
5240
|
const publishResult = await this.httpClient.publishIpns(
|
|
5123
|
-
|
|
5241
|
+
ipnsName,
|
|
5124
5242
|
marshalledRecord
|
|
5125
5243
|
);
|
|
5126
5244
|
if (!publishResult.success) {
|
|
@@ -5135,14 +5253,14 @@ var IpfsStorageProvider = class {
|
|
|
5135
5253
|
this.ipnsSequenceNumber = newSeq;
|
|
5136
5254
|
this.lastCid = cid;
|
|
5137
5255
|
this.remoteCid = cid;
|
|
5138
|
-
this.cache.setIpnsRecord(
|
|
5256
|
+
this.cache.setIpnsRecord(ipnsName, {
|
|
5139
5257
|
cid,
|
|
5140
5258
|
sequence: newSeq,
|
|
5141
5259
|
gateway: "local"
|
|
5142
5260
|
});
|
|
5143
5261
|
this.cache.setContent(cid, updatedData);
|
|
5144
|
-
this.cache.markIpnsFresh(
|
|
5145
|
-
await this.statePersistence.save(
|
|
5262
|
+
this.cache.markIpnsFresh(ipnsName);
|
|
5263
|
+
await this.statePersistence.save(ipnsName, {
|
|
5146
5264
|
sequenceNumber: newSeq.toString(),
|
|
5147
5265
|
lastCid: cid,
|
|
5148
5266
|
version: this.dataVersion
|
|
@@ -5194,7 +5312,8 @@ var IpfsStorageProvider = class {
|
|
|
5194
5312
|
const baseData = active.txfData ?? {
|
|
5195
5313
|
_meta: { version: 0, address: this.identity?.directAddress ?? "", formatVersion: "2.0", updatedAt: 0 }
|
|
5196
5314
|
};
|
|
5197
|
-
const
|
|
5315
|
+
const overrideIpns = active.capturedIpnsKeyPair && active.capturedIpnsName ? { keyPair: active.capturedIpnsKeyPair, name: active.capturedIpnsName } : void 0;
|
|
5316
|
+
const result = await this._doSave(baseData, overrideIpns);
|
|
5198
5317
|
if (!result.success) {
|
|
5199
5318
|
throw new SphereError(result.error ?? "Save failed", "STORAGE_ERROR");
|
|
5200
5319
|
}
|
|
@@ -5497,6 +5616,13 @@ var IpfsStorageProvider = class {
|
|
|
5497
5616
|
log(message) {
|
|
5498
5617
|
logger.debug("IPFS-Storage", message);
|
|
5499
5618
|
}
|
|
5619
|
+
/**
|
|
5620
|
+
* Create an independent instance for a different address.
|
|
5621
|
+
* Shares the same gateway/timeout config but has fresh IPNS state.
|
|
5622
|
+
*/
|
|
5623
|
+
createForAddress() {
|
|
5624
|
+
return new _IpfsStorageProvider(this._config, this._statePersistenceCtor);
|
|
5625
|
+
}
|
|
5500
5626
|
};
|
|
5501
5627
|
|
|
5502
5628
|
// impl/browser/ipfs/browser-ipfs-state-persistence.ts
|