@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.
@@ -591,36 +591,61 @@ var IndexedDBTokenStorageProvider = class {
591
591
  return meta !== null;
592
592
  }
593
593
  async clear() {
594
- if (this.db) {
595
- this.db.close();
596
- this.db = null;
597
- }
598
- this.status = "disconnected";
599
- const CLEAR_TIMEOUT = 1500;
600
- const withTimeout = (promise, ms, label) => Promise.race([
601
- promise,
602
- new Promise(
603
- (_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)
604
- )
605
- ]);
606
- const deleteDb = (name) => new Promise((resolve) => {
607
- const req = indexedDB.deleteDatabase(name);
608
- req.onsuccess = () => resolve();
609
- req.onerror = () => resolve();
610
- req.onblocked = () => resolve();
611
- });
594
+ const dbNames = [this.dbName];
612
595
  try {
596
+ if (this.db) {
597
+ await this.clearStore(STORE_TOKENS);
598
+ await this.clearStore(STORE_META);
599
+ this.db.close();
600
+ this.db = null;
601
+ }
602
+ this.status = "disconnected";
613
603
  if (typeof indexedDB.databases === "function") {
614
- const dbs = await withTimeout(
615
- indexedDB.databases(),
616
- CLEAR_TIMEOUT,
617
- "indexedDB.databases()"
618
- );
619
- await Promise.all(
620
- dbs.filter((db) => db.name?.startsWith(this.dbNamePrefix)).map((db) => deleteDb(db.name))
621
- );
622
- } else {
623
- await deleteDb(this.dbName);
604
+ try {
605
+ const dbs = await Promise.race([
606
+ indexedDB.databases(),
607
+ new Promise(
608
+ (_, reject) => setTimeout(() => reject(new Error("timeout")), 1500)
609
+ )
610
+ ]);
611
+ for (const dbInfo of dbs) {
612
+ if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
613
+ dbNames.push(dbInfo.name);
614
+ try {
615
+ const db = await new Promise((resolve, reject) => {
616
+ const req = indexedDB.open(dbInfo.name, DB_VERSION);
617
+ req.onsuccess = () => resolve(req.result);
618
+ req.onerror = () => reject(req.error);
619
+ req.onupgradeneeded = (e) => {
620
+ const d = e.target.result;
621
+ if (!d.objectStoreNames.contains(STORE_TOKENS)) d.createObjectStore(STORE_TOKENS, { keyPath: "id" });
622
+ if (!d.objectStoreNames.contains(STORE_META)) d.createObjectStore(STORE_META);
623
+ };
624
+ });
625
+ const clearTx = db.transaction([STORE_TOKENS, STORE_META], "readwrite");
626
+ clearTx.objectStore(STORE_TOKENS).clear();
627
+ clearTx.objectStore(STORE_META).clear();
628
+ await new Promise((resolve) => {
629
+ clearTx.oncomplete = () => resolve();
630
+ clearTx.onerror = () => resolve();
631
+ });
632
+ db.close();
633
+ } catch {
634
+ }
635
+ }
636
+ }
637
+ } catch {
638
+ }
639
+ }
640
+ for (const name of dbNames) {
641
+ try {
642
+ const req = indexedDB.deleteDatabase(name);
643
+ req.onerror = () => {
644
+ };
645
+ req.onblocked = () => {
646
+ };
647
+ } catch {
648
+ }
624
649
  }
625
650
  return true;
626
651
  } catch (err) {
@@ -1378,6 +1403,8 @@ var NostrTransportProvider = class {
1378
1403
  transferHandlers = /* @__PURE__ */ new Set();
1379
1404
  paymentRequestHandlers = /* @__PURE__ */ new Set();
1380
1405
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1406
+ readReceiptHandlers = /* @__PURE__ */ new Set();
1407
+ typingIndicatorHandlers = /* @__PURE__ */ new Set();
1381
1408
  broadcastHandlers = /* @__PURE__ */ new Map();
1382
1409
  eventCallbacks = /* @__PURE__ */ new Set();
1383
1410
  constructor(config) {
@@ -1629,6 +1656,18 @@ var NostrTransportProvider = class {
1629
1656
  const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1630
1657
  const giftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
1631
1658
  await this.publishEvent(giftWrap);
1659
+ const selfWrapContent = JSON.stringify({
1660
+ selfWrap: true,
1661
+ originalId: giftWrap.id,
1662
+ recipientPubkey,
1663
+ senderNametag,
1664
+ text: content
1665
+ });
1666
+ const selfPubkey = this.keyManager.getPublicKeyHex();
1667
+ const selfGiftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, selfPubkey, selfWrapContent);
1668
+ this.publishEvent(selfGiftWrap).catch((err) => {
1669
+ this.log("Self-wrap publish failed:", err);
1670
+ });
1632
1671
  this.emitEvent({
1633
1672
  type: "message:sent",
1634
1673
  timestamp: Date.now(),
@@ -1727,6 +1766,37 @@ var NostrTransportProvider = class {
1727
1766
  this.paymentRequestResponseHandlers.add(handler);
1728
1767
  return () => this.paymentRequestResponseHandlers.delete(handler);
1729
1768
  }
1769
+ // ===========================================================================
1770
+ // Read Receipts
1771
+ // ===========================================================================
1772
+ async sendReadReceipt(recipientTransportPubkey, messageEventId) {
1773
+ if (!this.keyManager) throw new Error("Not initialized");
1774
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1775
+ const event = import_nostr_js_sdk.NIP17.createReadReceipt(this.keyManager, nostrRecipient, messageEventId);
1776
+ await this.publishEvent(event);
1777
+ this.log("Sent read receipt for:", messageEventId, "to:", nostrRecipient.slice(0, 16));
1778
+ }
1779
+ onReadReceipt(handler) {
1780
+ this.readReceiptHandlers.add(handler);
1781
+ return () => this.readReceiptHandlers.delete(handler);
1782
+ }
1783
+ // ===========================================================================
1784
+ // Typing Indicators
1785
+ // ===========================================================================
1786
+ async sendTypingIndicator(recipientTransportPubkey) {
1787
+ if (!this.keyManager) throw new Error("Not initialized");
1788
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1789
+ const content = JSON.stringify({
1790
+ type: "typing",
1791
+ senderNametag: this.identity?.nametag
1792
+ });
1793
+ const event = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, nostrRecipient, content);
1794
+ await this.publishEvent(event);
1795
+ }
1796
+ onTypingIndicator(handler) {
1797
+ this.typingIndicatorHandlers.add(handler);
1798
+ return () => this.typingIndicatorHandlers.delete(handler);
1799
+ }
1730
1800
  /**
1731
1801
  * Resolve any identifier to full peer information.
1732
1802
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -2180,11 +2250,74 @@ var NostrTransportProvider = class {
2180
2250
  const pm = import_nostr_js_sdk.NIP17.unwrap(event, this.keyManager);
2181
2251
  this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
2182
2252
  if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
2183
- this.log("Skipping own message");
2253
+ try {
2254
+ const parsed = JSON.parse(pm.content);
2255
+ if (parsed?.selfWrap && parsed.recipientPubkey) {
2256
+ this.log("Self-wrap replay for recipient:", parsed.recipientPubkey?.slice(0, 16));
2257
+ const message2 = {
2258
+ id: parsed.originalId || pm.eventId,
2259
+ senderTransportPubkey: pm.senderPubkey,
2260
+ senderNametag: parsed.senderNametag,
2261
+ recipientTransportPubkey: parsed.recipientPubkey,
2262
+ content: parsed.text ?? "",
2263
+ timestamp: pm.timestamp * 1e3,
2264
+ encrypted: true,
2265
+ isSelfWrap: true
2266
+ };
2267
+ for (const handler of this.messageHandlers) {
2268
+ try {
2269
+ handler(message2);
2270
+ } catch (e) {
2271
+ this.log("Self-wrap handler error:", e);
2272
+ }
2273
+ }
2274
+ return;
2275
+ }
2276
+ } catch {
2277
+ }
2278
+ this.log("Skipping own non-self-wrap message");
2184
2279
  return;
2185
2280
  }
2186
- if (pm.kind !== import_nostr_js_sdk.EventKinds.CHAT_MESSAGE) {
2187
- this.log("Skipping non-chat message, kind:", pm.kind);
2281
+ if ((0, import_nostr_js_sdk.isReadReceipt)(pm)) {
2282
+ this.log("Read receipt from:", pm.senderPubkey?.slice(0, 16), "for:", pm.replyToEventId);
2283
+ if (pm.replyToEventId) {
2284
+ const receipt = {
2285
+ senderTransportPubkey: pm.senderPubkey,
2286
+ messageEventId: pm.replyToEventId,
2287
+ timestamp: pm.timestamp * 1e3
2288
+ };
2289
+ for (const handler of this.readReceiptHandlers) {
2290
+ try {
2291
+ handler(receipt);
2292
+ } catch (e) {
2293
+ this.log("Read receipt handler error:", e);
2294
+ }
2295
+ }
2296
+ }
2297
+ return;
2298
+ }
2299
+ try {
2300
+ const parsed = JSON.parse(pm.content);
2301
+ if (parsed?.type === "typing") {
2302
+ this.log("Typing indicator from:", pm.senderPubkey?.slice(0, 16));
2303
+ const indicator = {
2304
+ senderTransportPubkey: pm.senderPubkey,
2305
+ senderNametag: parsed.senderNametag,
2306
+ timestamp: pm.timestamp * 1e3
2307
+ };
2308
+ for (const handler of this.typingIndicatorHandlers) {
2309
+ try {
2310
+ handler(indicator);
2311
+ } catch (e) {
2312
+ this.log("Typing handler error:", e);
2313
+ }
2314
+ }
2315
+ return;
2316
+ }
2317
+ } catch {
2318
+ }
2319
+ if (!(0, import_nostr_js_sdk.isChatMessage)(pm)) {
2320
+ this.log("Skipping unknown message kind:", pm.kind);
2188
2321
  return;
2189
2322
  }
2190
2323
  let content = pm.content;
@@ -2199,7 +2332,9 @@ var NostrTransportProvider = class {
2199
2332
  }
2200
2333
  this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
2201
2334
  const message = {
2202
- id: pm.eventId,
2335
+ // Use outer gift wrap event.id so it matches the sender's stored giftWrap.id.
2336
+ // This ensures read receipts reference an ID the sender recognizes.
2337
+ id: event.id,
2203
2338
  senderTransportPubkey: pm.senderPubkey,
2204
2339
  senderNametag,
2205
2340
  content,