@unicitylabs/sphere-sdk 0.3.6 → 0.3.7

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.
@@ -533,36 +533,61 @@ var IndexedDBTokenStorageProvider = class {
533
533
  return meta !== null;
534
534
  }
535
535
  async clear() {
536
- if (this.db) {
537
- this.db.close();
538
- this.db = null;
539
- }
540
- this.status = "disconnected";
541
- const CLEAR_TIMEOUT = 1500;
542
- const withTimeout = (promise, ms, label) => Promise.race([
543
- promise,
544
- new Promise(
545
- (_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)
546
- )
547
- ]);
548
- const deleteDb = (name) => new Promise((resolve) => {
549
- const req = indexedDB.deleteDatabase(name);
550
- req.onsuccess = () => resolve();
551
- req.onerror = () => resolve();
552
- req.onblocked = () => resolve();
553
- });
536
+ const dbNames = [this.dbName];
554
537
  try {
538
+ if (this.db) {
539
+ await this.clearStore(STORE_TOKENS);
540
+ await this.clearStore(STORE_META);
541
+ this.db.close();
542
+ this.db = null;
543
+ }
544
+ this.status = "disconnected";
555
545
  if (typeof indexedDB.databases === "function") {
556
- const dbs = await withTimeout(
557
- indexedDB.databases(),
558
- CLEAR_TIMEOUT,
559
- "indexedDB.databases()"
560
- );
561
- await Promise.all(
562
- dbs.filter((db) => db.name?.startsWith(this.dbNamePrefix)).map((db) => deleteDb(db.name))
563
- );
564
- } else {
565
- await deleteDb(this.dbName);
546
+ try {
547
+ const dbs = await Promise.race([
548
+ indexedDB.databases(),
549
+ new Promise(
550
+ (_, reject) => setTimeout(() => reject(new Error("timeout")), 1500)
551
+ )
552
+ ]);
553
+ for (const dbInfo of dbs) {
554
+ if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
555
+ dbNames.push(dbInfo.name);
556
+ try {
557
+ const db = await new Promise((resolve, reject) => {
558
+ const req = indexedDB.open(dbInfo.name, DB_VERSION);
559
+ req.onsuccess = () => resolve(req.result);
560
+ req.onerror = () => reject(req.error);
561
+ req.onupgradeneeded = (e) => {
562
+ const d = e.target.result;
563
+ if (!d.objectStoreNames.contains(STORE_TOKENS)) d.createObjectStore(STORE_TOKENS, { keyPath: "id" });
564
+ if (!d.objectStoreNames.contains(STORE_META)) d.createObjectStore(STORE_META);
565
+ };
566
+ });
567
+ const clearTx = db.transaction([STORE_TOKENS, STORE_META], "readwrite");
568
+ clearTx.objectStore(STORE_TOKENS).clear();
569
+ clearTx.objectStore(STORE_META).clear();
570
+ await new Promise((resolve) => {
571
+ clearTx.oncomplete = () => resolve();
572
+ clearTx.onerror = () => resolve();
573
+ });
574
+ db.close();
575
+ } catch {
576
+ }
577
+ }
578
+ }
579
+ } catch {
580
+ }
581
+ }
582
+ for (const name of dbNames) {
583
+ try {
584
+ const req = indexedDB.deleteDatabase(name);
585
+ req.onerror = () => {
586
+ };
587
+ req.onblocked = () => {
588
+ };
589
+ } catch {
590
+ }
566
591
  }
567
592
  return true;
568
593
  } catch (err) {
@@ -1111,7 +1136,9 @@ import {
1111
1136
  EventKinds,
1112
1137
  hashNametag,
1113
1138
  NostrClient,
1114
- Filter
1139
+ Filter,
1140
+ isChatMessage,
1141
+ isReadReceipt
1115
1142
  } from "@unicitylabs/nostr-js-sdk";
1116
1143
 
1117
1144
  // core/crypto.ts
@@ -1329,6 +1356,8 @@ var NostrTransportProvider = class {
1329
1356
  transferHandlers = /* @__PURE__ */ new Set();
1330
1357
  paymentRequestHandlers = /* @__PURE__ */ new Set();
1331
1358
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1359
+ readReceiptHandlers = /* @__PURE__ */ new Set();
1360
+ typingIndicatorHandlers = /* @__PURE__ */ new Set();
1332
1361
  broadcastHandlers = /* @__PURE__ */ new Map();
1333
1362
  eventCallbacks = /* @__PURE__ */ new Set();
1334
1363
  constructor(config) {
@@ -1580,6 +1609,18 @@ var NostrTransportProvider = class {
1580
1609
  const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1581
1610
  const giftWrap = NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
1582
1611
  await this.publishEvent(giftWrap);
1612
+ const selfWrapContent = JSON.stringify({
1613
+ selfWrap: true,
1614
+ originalId: giftWrap.id,
1615
+ recipientPubkey,
1616
+ senderNametag,
1617
+ text: content
1618
+ });
1619
+ const selfPubkey = this.keyManager.getPublicKeyHex();
1620
+ const selfGiftWrap = NIP17.createGiftWrap(this.keyManager, selfPubkey, selfWrapContent);
1621
+ this.publishEvent(selfGiftWrap).catch((err) => {
1622
+ this.log("Self-wrap publish failed:", err);
1623
+ });
1583
1624
  this.emitEvent({
1584
1625
  type: "message:sent",
1585
1626
  timestamp: Date.now(),
@@ -1678,6 +1719,37 @@ var NostrTransportProvider = class {
1678
1719
  this.paymentRequestResponseHandlers.add(handler);
1679
1720
  return () => this.paymentRequestResponseHandlers.delete(handler);
1680
1721
  }
1722
+ // ===========================================================================
1723
+ // Read Receipts
1724
+ // ===========================================================================
1725
+ async sendReadReceipt(recipientTransportPubkey, messageEventId) {
1726
+ if (!this.keyManager) throw new Error("Not initialized");
1727
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1728
+ const event = NIP17.createReadReceipt(this.keyManager, nostrRecipient, messageEventId);
1729
+ await this.publishEvent(event);
1730
+ this.log("Sent read receipt for:", messageEventId, "to:", nostrRecipient.slice(0, 16));
1731
+ }
1732
+ onReadReceipt(handler) {
1733
+ this.readReceiptHandlers.add(handler);
1734
+ return () => this.readReceiptHandlers.delete(handler);
1735
+ }
1736
+ // ===========================================================================
1737
+ // Typing Indicators
1738
+ // ===========================================================================
1739
+ async sendTypingIndicator(recipientTransportPubkey) {
1740
+ if (!this.keyManager) throw new Error("Not initialized");
1741
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1742
+ const content = JSON.stringify({
1743
+ type: "typing",
1744
+ senderNametag: this.identity?.nametag
1745
+ });
1746
+ const event = NIP17.createGiftWrap(this.keyManager, nostrRecipient, content);
1747
+ await this.publishEvent(event);
1748
+ }
1749
+ onTypingIndicator(handler) {
1750
+ this.typingIndicatorHandlers.add(handler);
1751
+ return () => this.typingIndicatorHandlers.delete(handler);
1752
+ }
1681
1753
  /**
1682
1754
  * Resolve any identifier to full peer information.
1683
1755
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -2131,11 +2203,74 @@ var NostrTransportProvider = class {
2131
2203
  const pm = NIP17.unwrap(event, this.keyManager);
2132
2204
  this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
2133
2205
  if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
2134
- this.log("Skipping own message");
2206
+ try {
2207
+ const parsed = JSON.parse(pm.content);
2208
+ if (parsed?.selfWrap && parsed.recipientPubkey) {
2209
+ this.log("Self-wrap replay for recipient:", parsed.recipientPubkey?.slice(0, 16));
2210
+ const message2 = {
2211
+ id: parsed.originalId || pm.eventId,
2212
+ senderTransportPubkey: pm.senderPubkey,
2213
+ senderNametag: parsed.senderNametag,
2214
+ recipientTransportPubkey: parsed.recipientPubkey,
2215
+ content: parsed.text ?? "",
2216
+ timestamp: pm.timestamp * 1e3,
2217
+ encrypted: true,
2218
+ isSelfWrap: true
2219
+ };
2220
+ for (const handler of this.messageHandlers) {
2221
+ try {
2222
+ handler(message2);
2223
+ } catch (e) {
2224
+ this.log("Self-wrap handler error:", e);
2225
+ }
2226
+ }
2227
+ return;
2228
+ }
2229
+ } catch {
2230
+ }
2231
+ this.log("Skipping own non-self-wrap message");
2135
2232
  return;
2136
2233
  }
2137
- if (pm.kind !== EventKinds.CHAT_MESSAGE) {
2138
- this.log("Skipping non-chat message, kind:", pm.kind);
2234
+ if (isReadReceipt(pm)) {
2235
+ this.log("Read receipt from:", pm.senderPubkey?.slice(0, 16), "for:", pm.replyToEventId);
2236
+ if (pm.replyToEventId) {
2237
+ const receipt = {
2238
+ senderTransportPubkey: pm.senderPubkey,
2239
+ messageEventId: pm.replyToEventId,
2240
+ timestamp: pm.timestamp * 1e3
2241
+ };
2242
+ for (const handler of this.readReceiptHandlers) {
2243
+ try {
2244
+ handler(receipt);
2245
+ } catch (e) {
2246
+ this.log("Read receipt handler error:", e);
2247
+ }
2248
+ }
2249
+ }
2250
+ return;
2251
+ }
2252
+ try {
2253
+ const parsed = JSON.parse(pm.content);
2254
+ if (parsed?.type === "typing") {
2255
+ this.log("Typing indicator from:", pm.senderPubkey?.slice(0, 16));
2256
+ const indicator = {
2257
+ senderTransportPubkey: pm.senderPubkey,
2258
+ senderNametag: parsed.senderNametag,
2259
+ timestamp: pm.timestamp * 1e3
2260
+ };
2261
+ for (const handler of this.typingIndicatorHandlers) {
2262
+ try {
2263
+ handler(indicator);
2264
+ } catch (e) {
2265
+ this.log("Typing handler error:", e);
2266
+ }
2267
+ }
2268
+ return;
2269
+ }
2270
+ } catch {
2271
+ }
2272
+ if (!isChatMessage(pm)) {
2273
+ this.log("Skipping unknown message kind:", pm.kind);
2139
2274
  return;
2140
2275
  }
2141
2276
  let content = pm.content;
@@ -2150,7 +2285,9 @@ var NostrTransportProvider = class {
2150
2285
  }
2151
2286
  this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
2152
2287
  const message = {
2153
- id: pm.eventId,
2288
+ // Use outer gift wrap event.id so it matches the sender's stored giftWrap.id.
2289
+ // This ensures read receipts reference an ID the sender recognizes.
2290
+ id: event.id,
2154
2291
  senderTransportPubkey: pm.senderPubkey,
2155
2292
  senderNametag,
2156
2293
  content,