@unicitylabs/sphere-sdk 0.2.2 → 0.2.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/README.md +22 -69
- package/dist/core/index.cjs +167 -62
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +16 -5
- package/dist/core/index.d.ts +16 -5
- package/dist/core/index.js +167 -62
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +89 -12
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +89 -12
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +3 -1
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +3 -1
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +77 -12
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +21 -0
- package/dist/impl/nodejs/index.d.ts +21 -0
- package/dist/impl/nodejs/index.js +77 -12
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +167 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -5
- package/dist/index.d.ts +18 -5
- package/dist/index.js +167 -62
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -82,7 +82,9 @@ var STORAGE_KEYS_GLOBAL = {
|
|
|
82
82
|
/** Nametag cache per address (separate from tracked addresses registry) */
|
|
83
83
|
ADDRESS_NAMETAGS: "address_nametags",
|
|
84
84
|
/** Active addresses registry (JSON: TrackedAddressesStorage) */
|
|
85
|
-
TRACKED_ADDRESSES: "tracked_addresses"
|
|
85
|
+
TRACKED_ADDRESSES: "tracked_addresses",
|
|
86
|
+
/** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
|
|
87
|
+
LAST_WALLET_EVENT_TS: "last_wallet_event_ts"
|
|
86
88
|
};
|
|
87
89
|
var STORAGE_KEYS_ADDRESS = {
|
|
88
90
|
/** Pending transfers for this address */
|
|
@@ -555,6 +557,13 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
555
557
|
this.db = null;
|
|
556
558
|
}
|
|
557
559
|
this.status = "disconnected";
|
|
560
|
+
const CLEAR_TIMEOUT = 1500;
|
|
561
|
+
const withTimeout = (promise, ms, label) => Promise.race([
|
|
562
|
+
promise,
|
|
563
|
+
new Promise(
|
|
564
|
+
(_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)
|
|
565
|
+
)
|
|
566
|
+
]);
|
|
558
567
|
const deleteDb = (name) => new Promise((resolve) => {
|
|
559
568
|
const req = indexedDB.deleteDatabase(name);
|
|
560
569
|
req.onsuccess = () => resolve();
|
|
@@ -563,7 +572,11 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
563
572
|
});
|
|
564
573
|
try {
|
|
565
574
|
if (typeof indexedDB.databases === "function") {
|
|
566
|
-
const dbs = await
|
|
575
|
+
const dbs = await withTimeout(
|
|
576
|
+
indexedDB.databases(),
|
|
577
|
+
CLEAR_TIMEOUT,
|
|
578
|
+
"indexedDB.databases()"
|
|
579
|
+
);
|
|
567
580
|
await Promise.all(
|
|
568
581
|
dbs.filter((db) => db.name?.startsWith(this.dbNamePrefix)).map((db) => deleteDb(db.name))
|
|
569
582
|
);
|
|
@@ -571,7 +584,8 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
571
584
|
await deleteDb(this.dbName);
|
|
572
585
|
}
|
|
573
586
|
return true;
|
|
574
|
-
} catch {
|
|
587
|
+
} catch (err) {
|
|
588
|
+
console.warn("[IndexedDBTokenStorage] clear() failed:", err);
|
|
575
589
|
return false;
|
|
576
590
|
}
|
|
577
591
|
}
|
|
@@ -1325,6 +1339,9 @@ var NostrTransportProvider = class {
|
|
|
1325
1339
|
type = "p2p";
|
|
1326
1340
|
description = "P2P messaging via Nostr protocol";
|
|
1327
1341
|
config;
|
|
1342
|
+
storage = null;
|
|
1343
|
+
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
1344
|
+
lastEventTs = 0;
|
|
1328
1345
|
identity = null;
|
|
1329
1346
|
keyManager = null;
|
|
1330
1347
|
status = "disconnected";
|
|
@@ -1350,6 +1367,7 @@ var NostrTransportProvider = class {
|
|
|
1350
1367
|
createWebSocket: config.createWebSocket,
|
|
1351
1368
|
generateUUID: config.generateUUID ?? defaultUUIDGenerator
|
|
1352
1369
|
};
|
|
1370
|
+
this.storage = config.storage ?? null;
|
|
1353
1371
|
}
|
|
1354
1372
|
// ===========================================================================
|
|
1355
1373
|
// BaseProvider Implementation
|
|
@@ -1388,7 +1406,14 @@ var NostrTransportProvider = class {
|
|
|
1388
1406
|
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1389
1407
|
}
|
|
1390
1408
|
});
|
|
1391
|
-
await
|
|
1409
|
+
await Promise.race([
|
|
1410
|
+
this.nostrClient.connect(...this.config.relays),
|
|
1411
|
+
new Promise(
|
|
1412
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
1413
|
+
`Transport connection timed out after ${this.config.timeout}ms`
|
|
1414
|
+
)), this.config.timeout)
|
|
1415
|
+
)
|
|
1416
|
+
]);
|
|
1392
1417
|
if (!this.nostrClient.isConnected()) {
|
|
1393
1418
|
throw new Error("Failed to connect to any relay");
|
|
1394
1419
|
}
|
|
@@ -1396,7 +1421,7 @@ var NostrTransportProvider = class {
|
|
|
1396
1421
|
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1397
1422
|
this.log("Connected to", this.nostrClient.getConnectedRelays().size, "relays");
|
|
1398
1423
|
if (this.identity) {
|
|
1399
|
-
this.subscribeToEvents();
|
|
1424
|
+
await this.subscribeToEvents();
|
|
1400
1425
|
}
|
|
1401
1426
|
} catch (error) {
|
|
1402
1427
|
this.status = "error";
|
|
@@ -1549,11 +1574,18 @@ var NostrTransportProvider = class {
|
|
|
1549
1574
|
this.log("NostrClient reconnected to relay:", url);
|
|
1550
1575
|
}
|
|
1551
1576
|
});
|
|
1552
|
-
await
|
|
1553
|
-
|
|
1577
|
+
await Promise.race([
|
|
1578
|
+
this.nostrClient.connect(...this.config.relays),
|
|
1579
|
+
new Promise(
|
|
1580
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
1581
|
+
`Transport reconnection timed out after ${this.config.timeout}ms`
|
|
1582
|
+
)), this.config.timeout)
|
|
1583
|
+
)
|
|
1584
|
+
]);
|
|
1585
|
+
await this.subscribeToEvents();
|
|
1554
1586
|
oldClient.disconnect();
|
|
1555
1587
|
} else if (this.isConnected()) {
|
|
1556
|
-
this.subscribeToEvents();
|
|
1588
|
+
await this.subscribeToEvents();
|
|
1557
1589
|
}
|
|
1558
1590
|
}
|
|
1559
1591
|
/**
|
|
@@ -2081,10 +2113,31 @@ var NostrTransportProvider = class {
|
|
|
2081
2113
|
this.handleBroadcast(event);
|
|
2082
2114
|
break;
|
|
2083
2115
|
}
|
|
2116
|
+
if (event.created_at && this.storage && this.keyManager) {
|
|
2117
|
+
const kind = event.kind;
|
|
2118
|
+
if (kind === EVENT_KINDS.DIRECT_MESSAGE || kind === EVENT_KINDS.TOKEN_TRANSFER || kind === EVENT_KINDS.PAYMENT_REQUEST || kind === EVENT_KINDS.PAYMENT_REQUEST_RESPONSE) {
|
|
2119
|
+
this.updateLastEventTimestamp(event.created_at);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2084
2122
|
} catch (error) {
|
|
2085
2123
|
this.log("Failed to handle event:", error);
|
|
2086
2124
|
}
|
|
2087
2125
|
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Save the max event timestamp to storage (fire-and-forget, no await needed by caller).
|
|
2128
|
+
* Uses in-memory `lastEventTs` to avoid read-before-write race conditions
|
|
2129
|
+
* when multiple events arrive in quick succession.
|
|
2130
|
+
*/
|
|
2131
|
+
updateLastEventTimestamp(createdAt) {
|
|
2132
|
+
if (!this.storage || !this.keyManager) return;
|
|
2133
|
+
if (createdAt <= this.lastEventTs) return;
|
|
2134
|
+
this.lastEventTs = createdAt;
|
|
2135
|
+
const pubkey = this.keyManager.getPublicKeyHex();
|
|
2136
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${pubkey.slice(0, 16)}`;
|
|
2137
|
+
this.storage.set(storageKey, createdAt.toString()).catch((err) => {
|
|
2138
|
+
this.log("Failed to save last event timestamp:", err);
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2088
2141
|
async handleDirectMessage(event) {
|
|
2089
2142
|
this.log("Ignoring NIP-04 kind 4 event (DMs use NIP-17):", event.id?.slice(0, 12));
|
|
2090
2143
|
}
|
|
@@ -2163,6 +2216,7 @@ var NostrTransportProvider = class {
|
|
|
2163
2216
|
const request = {
|
|
2164
2217
|
id: event.id,
|
|
2165
2218
|
senderTransportPubkey: event.pubkey,
|
|
2219
|
+
senderNametag: requestData.recipientNametag,
|
|
2166
2220
|
request: {
|
|
2167
2221
|
requestId: requestData.requestId,
|
|
2168
2222
|
amount: requestData.amount,
|
|
@@ -2317,7 +2371,7 @@ var NostrTransportProvider = class {
|
|
|
2317
2371
|
// Track subscription IDs for cleanup
|
|
2318
2372
|
walletSubscriptionId = null;
|
|
2319
2373
|
chatSubscriptionId = null;
|
|
2320
|
-
subscribeToEvents() {
|
|
2374
|
+
async subscribeToEvents() {
|
|
2321
2375
|
this.log("subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
|
|
2322
2376
|
if (!this.identity || !this.keyManager || !this.nostrClient) {
|
|
2323
2377
|
this.log("subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
|
|
@@ -2337,6 +2391,27 @@ var NostrTransportProvider = class {
|
|
|
2337
2391
|
}
|
|
2338
2392
|
const nostrPubkey = this.keyManager.getPublicKeyHex();
|
|
2339
2393
|
this.log("Subscribing with Nostr pubkey:", nostrPubkey);
|
|
2394
|
+
let since;
|
|
2395
|
+
if (this.storage) {
|
|
2396
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${nostrPubkey.slice(0, 16)}`;
|
|
2397
|
+
try {
|
|
2398
|
+
const stored = await this.storage.get(storageKey);
|
|
2399
|
+
if (stored) {
|
|
2400
|
+
since = parseInt(stored, 10);
|
|
2401
|
+
this.lastEventTs = since;
|
|
2402
|
+
this.log("Resuming from stored event timestamp:", since);
|
|
2403
|
+
} else {
|
|
2404
|
+
since = Math.floor(Date.now() / 1e3);
|
|
2405
|
+
this.log("No stored timestamp, starting from now:", since);
|
|
2406
|
+
}
|
|
2407
|
+
} catch (err) {
|
|
2408
|
+
this.log("Failed to read last event timestamp, falling back to now:", err);
|
|
2409
|
+
since = Math.floor(Date.now() / 1e3);
|
|
2410
|
+
}
|
|
2411
|
+
} else {
|
|
2412
|
+
since = Math.floor(Date.now() / 1e3) - 86400;
|
|
2413
|
+
this.log("No storage adapter, using 24h fallback");
|
|
2414
|
+
}
|
|
2340
2415
|
const walletFilter = new import_nostr_js_sdk.Filter();
|
|
2341
2416
|
walletFilter.kinds = [
|
|
2342
2417
|
EVENT_KINDS.DIRECT_MESSAGE,
|
|
@@ -2345,7 +2420,7 @@ var NostrTransportProvider = class {
|
|
|
2345
2420
|
EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
|
|
2346
2421
|
];
|
|
2347
2422
|
walletFilter["#p"] = [nostrPubkey];
|
|
2348
|
-
walletFilter.since =
|
|
2423
|
+
walletFilter.since = since;
|
|
2349
2424
|
this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
|
|
2350
2425
|
onEvent: (event) => {
|
|
2351
2426
|
this.log("Received wallet event kind:", event.kind, "id:", event.id?.slice(0, 12));
|
|
@@ -3255,15 +3330,17 @@ function createBrowserProviders(config) {
|
|
|
3255
3330
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
3256
3331
|
const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
|
|
3257
3332
|
const priceConfig = resolvePriceConfig(config?.price);
|
|
3333
|
+
const storage = createLocalStorageProvider(config?.storage);
|
|
3258
3334
|
return {
|
|
3259
|
-
storage
|
|
3335
|
+
storage,
|
|
3260
3336
|
transport: createNostrTransportProvider({
|
|
3261
3337
|
relays: transportConfig.relays,
|
|
3262
3338
|
timeout: transportConfig.timeout,
|
|
3263
3339
|
autoReconnect: transportConfig.autoReconnect,
|
|
3264
3340
|
reconnectDelay: transportConfig.reconnectDelay,
|
|
3265
3341
|
maxReconnectAttempts: transportConfig.maxReconnectAttempts,
|
|
3266
|
-
debug: transportConfig.debug
|
|
3342
|
+
debug: transportConfig.debug,
|
|
3343
|
+
storage
|
|
3267
3344
|
}),
|
|
3268
3345
|
oracle: createUnicityAggregatorProvider({
|
|
3269
3346
|
url: oracleConfig.url,
|