@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
|
@@ -24,7 +24,9 @@ var STORAGE_KEYS_GLOBAL = {
|
|
|
24
24
|
/** Nametag cache per address (separate from tracked addresses registry) */
|
|
25
25
|
ADDRESS_NAMETAGS: "address_nametags",
|
|
26
26
|
/** Active addresses registry (JSON: TrackedAddressesStorage) */
|
|
27
|
-
TRACKED_ADDRESSES: "tracked_addresses"
|
|
27
|
+
TRACKED_ADDRESSES: "tracked_addresses",
|
|
28
|
+
/** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
|
|
29
|
+
LAST_WALLET_EVENT_TS: "last_wallet_event_ts"
|
|
28
30
|
};
|
|
29
31
|
var STORAGE_KEYS_ADDRESS = {
|
|
30
32
|
/** Pending transfers for this address */
|
|
@@ -497,6 +499,13 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
497
499
|
this.db = null;
|
|
498
500
|
}
|
|
499
501
|
this.status = "disconnected";
|
|
502
|
+
const CLEAR_TIMEOUT = 1500;
|
|
503
|
+
const withTimeout = (promise, ms, label) => Promise.race([
|
|
504
|
+
promise,
|
|
505
|
+
new Promise(
|
|
506
|
+
(_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)
|
|
507
|
+
)
|
|
508
|
+
]);
|
|
500
509
|
const deleteDb = (name) => new Promise((resolve) => {
|
|
501
510
|
const req = indexedDB.deleteDatabase(name);
|
|
502
511
|
req.onsuccess = () => resolve();
|
|
@@ -505,7 +514,11 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
505
514
|
});
|
|
506
515
|
try {
|
|
507
516
|
if (typeof indexedDB.databases === "function") {
|
|
508
|
-
const dbs = await
|
|
517
|
+
const dbs = await withTimeout(
|
|
518
|
+
indexedDB.databases(),
|
|
519
|
+
CLEAR_TIMEOUT,
|
|
520
|
+
"indexedDB.databases()"
|
|
521
|
+
);
|
|
509
522
|
await Promise.all(
|
|
510
523
|
dbs.filter((db) => db.name?.startsWith(this.dbNamePrefix)).map((db) => deleteDb(db.name))
|
|
511
524
|
);
|
|
@@ -513,7 +526,8 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
513
526
|
await deleteDb(this.dbName);
|
|
514
527
|
}
|
|
515
528
|
return true;
|
|
516
|
-
} catch {
|
|
529
|
+
} catch (err) {
|
|
530
|
+
console.warn("[IndexedDBTokenStorage] clear() failed:", err);
|
|
517
531
|
return false;
|
|
518
532
|
}
|
|
519
533
|
}
|
|
@@ -1276,6 +1290,9 @@ var NostrTransportProvider = class {
|
|
|
1276
1290
|
type = "p2p";
|
|
1277
1291
|
description = "P2P messaging via Nostr protocol";
|
|
1278
1292
|
config;
|
|
1293
|
+
storage = null;
|
|
1294
|
+
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
1295
|
+
lastEventTs = 0;
|
|
1279
1296
|
identity = null;
|
|
1280
1297
|
keyManager = null;
|
|
1281
1298
|
status = "disconnected";
|
|
@@ -1301,6 +1318,7 @@ var NostrTransportProvider = class {
|
|
|
1301
1318
|
createWebSocket: config.createWebSocket,
|
|
1302
1319
|
generateUUID: config.generateUUID ?? defaultUUIDGenerator
|
|
1303
1320
|
};
|
|
1321
|
+
this.storage = config.storage ?? null;
|
|
1304
1322
|
}
|
|
1305
1323
|
// ===========================================================================
|
|
1306
1324
|
// BaseProvider Implementation
|
|
@@ -1339,7 +1357,14 @@ var NostrTransportProvider = class {
|
|
|
1339
1357
|
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1340
1358
|
}
|
|
1341
1359
|
});
|
|
1342
|
-
await
|
|
1360
|
+
await Promise.race([
|
|
1361
|
+
this.nostrClient.connect(...this.config.relays),
|
|
1362
|
+
new Promise(
|
|
1363
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
1364
|
+
`Transport connection timed out after ${this.config.timeout}ms`
|
|
1365
|
+
)), this.config.timeout)
|
|
1366
|
+
)
|
|
1367
|
+
]);
|
|
1343
1368
|
if (!this.nostrClient.isConnected()) {
|
|
1344
1369
|
throw new Error("Failed to connect to any relay");
|
|
1345
1370
|
}
|
|
@@ -1347,7 +1372,7 @@ var NostrTransportProvider = class {
|
|
|
1347
1372
|
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1348
1373
|
this.log("Connected to", this.nostrClient.getConnectedRelays().size, "relays");
|
|
1349
1374
|
if (this.identity) {
|
|
1350
|
-
this.subscribeToEvents();
|
|
1375
|
+
await this.subscribeToEvents();
|
|
1351
1376
|
}
|
|
1352
1377
|
} catch (error) {
|
|
1353
1378
|
this.status = "error";
|
|
@@ -1500,11 +1525,18 @@ var NostrTransportProvider = class {
|
|
|
1500
1525
|
this.log("NostrClient reconnected to relay:", url);
|
|
1501
1526
|
}
|
|
1502
1527
|
});
|
|
1503
|
-
await
|
|
1504
|
-
|
|
1528
|
+
await Promise.race([
|
|
1529
|
+
this.nostrClient.connect(...this.config.relays),
|
|
1530
|
+
new Promise(
|
|
1531
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
1532
|
+
`Transport reconnection timed out after ${this.config.timeout}ms`
|
|
1533
|
+
)), this.config.timeout)
|
|
1534
|
+
)
|
|
1535
|
+
]);
|
|
1536
|
+
await this.subscribeToEvents();
|
|
1505
1537
|
oldClient.disconnect();
|
|
1506
1538
|
} else if (this.isConnected()) {
|
|
1507
|
-
this.subscribeToEvents();
|
|
1539
|
+
await this.subscribeToEvents();
|
|
1508
1540
|
}
|
|
1509
1541
|
}
|
|
1510
1542
|
/**
|
|
@@ -2032,10 +2064,31 @@ var NostrTransportProvider = class {
|
|
|
2032
2064
|
this.handleBroadcast(event);
|
|
2033
2065
|
break;
|
|
2034
2066
|
}
|
|
2067
|
+
if (event.created_at && this.storage && this.keyManager) {
|
|
2068
|
+
const kind = event.kind;
|
|
2069
|
+
if (kind === EVENT_KINDS.DIRECT_MESSAGE || kind === EVENT_KINDS.TOKEN_TRANSFER || kind === EVENT_KINDS.PAYMENT_REQUEST || kind === EVENT_KINDS.PAYMENT_REQUEST_RESPONSE) {
|
|
2070
|
+
this.updateLastEventTimestamp(event.created_at);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2035
2073
|
} catch (error) {
|
|
2036
2074
|
this.log("Failed to handle event:", error);
|
|
2037
2075
|
}
|
|
2038
2076
|
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Save the max event timestamp to storage (fire-and-forget, no await needed by caller).
|
|
2079
|
+
* Uses in-memory `lastEventTs` to avoid read-before-write race conditions
|
|
2080
|
+
* when multiple events arrive in quick succession.
|
|
2081
|
+
*/
|
|
2082
|
+
updateLastEventTimestamp(createdAt) {
|
|
2083
|
+
if (!this.storage || !this.keyManager) return;
|
|
2084
|
+
if (createdAt <= this.lastEventTs) return;
|
|
2085
|
+
this.lastEventTs = createdAt;
|
|
2086
|
+
const pubkey = this.keyManager.getPublicKeyHex();
|
|
2087
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${pubkey.slice(0, 16)}`;
|
|
2088
|
+
this.storage.set(storageKey, createdAt.toString()).catch((err) => {
|
|
2089
|
+
this.log("Failed to save last event timestamp:", err);
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2039
2092
|
async handleDirectMessage(event) {
|
|
2040
2093
|
this.log("Ignoring NIP-04 kind 4 event (DMs use NIP-17):", event.id?.slice(0, 12));
|
|
2041
2094
|
}
|
|
@@ -2114,6 +2167,7 @@ var NostrTransportProvider = class {
|
|
|
2114
2167
|
const request = {
|
|
2115
2168
|
id: event.id,
|
|
2116
2169
|
senderTransportPubkey: event.pubkey,
|
|
2170
|
+
senderNametag: requestData.recipientNametag,
|
|
2117
2171
|
request: {
|
|
2118
2172
|
requestId: requestData.requestId,
|
|
2119
2173
|
amount: requestData.amount,
|
|
@@ -2268,7 +2322,7 @@ var NostrTransportProvider = class {
|
|
|
2268
2322
|
// Track subscription IDs for cleanup
|
|
2269
2323
|
walletSubscriptionId = null;
|
|
2270
2324
|
chatSubscriptionId = null;
|
|
2271
|
-
subscribeToEvents() {
|
|
2325
|
+
async subscribeToEvents() {
|
|
2272
2326
|
this.log("subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
|
|
2273
2327
|
if (!this.identity || !this.keyManager || !this.nostrClient) {
|
|
2274
2328
|
this.log("subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
|
|
@@ -2288,6 +2342,27 @@ var NostrTransportProvider = class {
|
|
|
2288
2342
|
}
|
|
2289
2343
|
const nostrPubkey = this.keyManager.getPublicKeyHex();
|
|
2290
2344
|
this.log("Subscribing with Nostr pubkey:", nostrPubkey);
|
|
2345
|
+
let since;
|
|
2346
|
+
if (this.storage) {
|
|
2347
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${nostrPubkey.slice(0, 16)}`;
|
|
2348
|
+
try {
|
|
2349
|
+
const stored = await this.storage.get(storageKey);
|
|
2350
|
+
if (stored) {
|
|
2351
|
+
since = parseInt(stored, 10);
|
|
2352
|
+
this.lastEventTs = since;
|
|
2353
|
+
this.log("Resuming from stored event timestamp:", since);
|
|
2354
|
+
} else {
|
|
2355
|
+
since = Math.floor(Date.now() / 1e3);
|
|
2356
|
+
this.log("No stored timestamp, starting from now:", since);
|
|
2357
|
+
}
|
|
2358
|
+
} catch (err) {
|
|
2359
|
+
this.log("Failed to read last event timestamp, falling back to now:", err);
|
|
2360
|
+
since = Math.floor(Date.now() / 1e3);
|
|
2361
|
+
}
|
|
2362
|
+
} else {
|
|
2363
|
+
since = Math.floor(Date.now() / 1e3) - 86400;
|
|
2364
|
+
this.log("No storage adapter, using 24h fallback");
|
|
2365
|
+
}
|
|
2291
2366
|
const walletFilter = new Filter();
|
|
2292
2367
|
walletFilter.kinds = [
|
|
2293
2368
|
EVENT_KINDS.DIRECT_MESSAGE,
|
|
@@ -2296,7 +2371,7 @@ var NostrTransportProvider = class {
|
|
|
2296
2371
|
EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
|
|
2297
2372
|
];
|
|
2298
2373
|
walletFilter["#p"] = [nostrPubkey];
|
|
2299
|
-
walletFilter.since =
|
|
2374
|
+
walletFilter.since = since;
|
|
2300
2375
|
this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
|
|
2301
2376
|
onEvent: (event) => {
|
|
2302
2377
|
this.log("Received wallet event kind:", event.kind, "id:", event.id?.slice(0, 12));
|
|
@@ -3206,15 +3281,17 @@ function createBrowserProviders(config) {
|
|
|
3206
3281
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
3207
3282
|
const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
|
|
3208
3283
|
const priceConfig = resolvePriceConfig(config?.price);
|
|
3284
|
+
const storage = createLocalStorageProvider(config?.storage);
|
|
3209
3285
|
return {
|
|
3210
|
-
storage
|
|
3286
|
+
storage,
|
|
3211
3287
|
transport: createNostrTransportProvider({
|
|
3212
3288
|
relays: transportConfig.relays,
|
|
3213
3289
|
timeout: transportConfig.timeout,
|
|
3214
3290
|
autoReconnect: transportConfig.autoReconnect,
|
|
3215
3291
|
reconnectDelay: transportConfig.reconnectDelay,
|
|
3216
3292
|
maxReconnectAttempts: transportConfig.maxReconnectAttempts,
|
|
3217
|
-
debug: transportConfig.debug
|
|
3293
|
+
debug: transportConfig.debug,
|
|
3294
|
+
storage
|
|
3218
3295
|
}),
|
|
3219
3296
|
oracle: createUnicityAggregatorProvider({
|
|
3220
3297
|
url: oracleConfig.url,
|