@unicitylabs/sphere-sdk 0.3.8 → 0.3.9

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.
Files changed (43) hide show
  1. package/dist/connect/index.cjs +770 -0
  2. package/dist/connect/index.cjs.map +1 -0
  3. package/dist/connect/index.d.cts +312 -0
  4. package/dist/connect/index.d.ts +312 -0
  5. package/dist/connect/index.js +747 -0
  6. package/dist/connect/index.js.map +1 -0
  7. package/dist/core/index.cjs +87 -7
  8. package/dist/core/index.cjs.map +1 -1
  9. package/dist/core/index.d.cts +57 -1
  10. package/dist/core/index.d.ts +57 -1
  11. package/dist/core/index.js +87 -7
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/impl/browser/connect/index.cjs +271 -0
  14. package/dist/impl/browser/connect/index.cjs.map +1 -0
  15. package/dist/impl/browser/connect/index.d.cts +137 -0
  16. package/dist/impl/browser/connect/index.d.ts +137 -0
  17. package/dist/impl/browser/connect/index.js +248 -0
  18. package/dist/impl/browser/connect/index.js.map +1 -0
  19. package/dist/impl/browser/index.cjs +167 -32
  20. package/dist/impl/browser/index.cjs.map +1 -1
  21. package/dist/impl/browser/index.js +170 -33
  22. package/dist/impl/browser/index.js.map +1 -1
  23. package/dist/impl/browser/ipfs.cjs.map +1 -1
  24. package/dist/impl/browser/ipfs.js.map +1 -1
  25. package/dist/impl/nodejs/connect/index.cjs +372 -0
  26. package/dist/impl/nodejs/connect/index.cjs.map +1 -0
  27. package/dist/impl/nodejs/connect/index.d.cts +178 -0
  28. package/dist/impl/nodejs/connect/index.d.ts +178 -0
  29. package/dist/impl/nodejs/connect/index.js +333 -0
  30. package/dist/impl/nodejs/connect/index.js.map +1 -0
  31. package/dist/impl/nodejs/index.cjs +114 -4
  32. package/dist/impl/nodejs/index.cjs.map +1 -1
  33. package/dist/impl/nodejs/index.d.cts +49 -0
  34. package/dist/impl/nodejs/index.d.ts +49 -0
  35. package/dist/impl/nodejs/index.js +117 -5
  36. package/dist/impl/nodejs/index.js.map +1 -1
  37. package/dist/index.cjs +87 -7
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +57 -1
  40. package/dist/index.d.ts +57 -1
  41. package/dist/index.js +87 -7
  42. package/dist/index.js.map +1 -1
  43. package/package.json +31 -1
@@ -536,36 +536,61 @@ var IndexedDBTokenStorageProvider = class {
536
536
  return meta !== null;
537
537
  }
538
538
  async clear() {
539
- if (this.db) {
540
- this.db.close();
541
- this.db = null;
542
- }
543
- this.status = "disconnected";
544
- const CLEAR_TIMEOUT = 1500;
545
- const withTimeout = (promise, ms, label) => Promise.race([
546
- promise,
547
- new Promise(
548
- (_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)
549
- )
550
- ]);
551
- const deleteDb = (name) => new Promise((resolve) => {
552
- const req = indexedDB.deleteDatabase(name);
553
- req.onsuccess = () => resolve();
554
- req.onerror = () => resolve();
555
- req.onblocked = () => resolve();
556
- });
539
+ const dbNames = [this.dbName];
557
540
  try {
541
+ if (this.db) {
542
+ await this.clearStore(STORE_TOKENS);
543
+ await this.clearStore(STORE_META);
544
+ this.db.close();
545
+ this.db = null;
546
+ }
547
+ this.status = "disconnected";
558
548
  if (typeof indexedDB.databases === "function") {
559
- const dbs = await withTimeout(
560
- indexedDB.databases(),
561
- CLEAR_TIMEOUT,
562
- "indexedDB.databases()"
563
- );
564
- await Promise.all(
565
- dbs.filter((db) => db.name?.startsWith(this.dbNamePrefix)).map((db) => deleteDb(db.name))
566
- );
567
- } else {
568
- await deleteDb(this.dbName);
549
+ try {
550
+ const dbs = await Promise.race([
551
+ indexedDB.databases(),
552
+ new Promise(
553
+ (_, reject) => setTimeout(() => reject(new Error("timeout")), 1500)
554
+ )
555
+ ]);
556
+ for (const dbInfo of dbs) {
557
+ if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
558
+ dbNames.push(dbInfo.name);
559
+ try {
560
+ const db = await new Promise((resolve, reject) => {
561
+ const req = indexedDB.open(dbInfo.name, DB_VERSION);
562
+ req.onsuccess = () => resolve(req.result);
563
+ req.onerror = () => reject(req.error);
564
+ req.onupgradeneeded = (e) => {
565
+ const d = e.target.result;
566
+ if (!d.objectStoreNames.contains(STORE_TOKENS)) d.createObjectStore(STORE_TOKENS, { keyPath: "id" });
567
+ if (!d.objectStoreNames.contains(STORE_META)) d.createObjectStore(STORE_META);
568
+ };
569
+ });
570
+ const clearTx = db.transaction([STORE_TOKENS, STORE_META], "readwrite");
571
+ clearTx.objectStore(STORE_TOKENS).clear();
572
+ clearTx.objectStore(STORE_META).clear();
573
+ await new Promise((resolve) => {
574
+ clearTx.oncomplete = () => resolve();
575
+ clearTx.onerror = () => resolve();
576
+ });
577
+ db.close();
578
+ } catch {
579
+ }
580
+ }
581
+ }
582
+ } catch {
583
+ }
584
+ }
585
+ for (const name of dbNames) {
586
+ try {
587
+ const req = indexedDB.deleteDatabase(name);
588
+ req.onerror = () => {
589
+ };
590
+ req.onblocked = () => {
591
+ };
592
+ } catch {
593
+ }
569
594
  }
570
595
  return true;
571
596
  } catch (err) {
@@ -1114,7 +1139,9 @@ import {
1114
1139
  EventKinds,
1115
1140
  hashNametag,
1116
1141
  NostrClient,
1117
- Filter
1142
+ Filter,
1143
+ isChatMessage,
1144
+ isReadReceipt
1118
1145
  } from "@unicitylabs/nostr-js-sdk";
1119
1146
 
1120
1147
  // core/crypto.ts
@@ -1332,6 +1359,8 @@ var NostrTransportProvider = class {
1332
1359
  transferHandlers = /* @__PURE__ */ new Set();
1333
1360
  paymentRequestHandlers = /* @__PURE__ */ new Set();
1334
1361
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1362
+ readReceiptHandlers = /* @__PURE__ */ new Set();
1363
+ typingIndicatorHandlers = /* @__PURE__ */ new Set();
1335
1364
  broadcastHandlers = /* @__PURE__ */ new Map();
1336
1365
  eventCallbacks = /* @__PURE__ */ new Set();
1337
1366
  constructor(config) {
@@ -1583,6 +1612,18 @@ var NostrTransportProvider = class {
1583
1612
  const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1584
1613
  const giftWrap = NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
1585
1614
  await this.publishEvent(giftWrap);
1615
+ const selfWrapContent = JSON.stringify({
1616
+ selfWrap: true,
1617
+ originalId: giftWrap.id,
1618
+ recipientPubkey,
1619
+ senderNametag,
1620
+ text: content
1621
+ });
1622
+ const selfPubkey = this.keyManager.getPublicKeyHex();
1623
+ const selfGiftWrap = NIP17.createGiftWrap(this.keyManager, selfPubkey, selfWrapContent);
1624
+ this.publishEvent(selfGiftWrap).catch((err) => {
1625
+ this.log("Self-wrap publish failed:", err);
1626
+ });
1586
1627
  this.emitEvent({
1587
1628
  type: "message:sent",
1588
1629
  timestamp: Date.now(),
@@ -1681,6 +1722,37 @@ var NostrTransportProvider = class {
1681
1722
  this.paymentRequestResponseHandlers.add(handler);
1682
1723
  return () => this.paymentRequestResponseHandlers.delete(handler);
1683
1724
  }
1725
+ // ===========================================================================
1726
+ // Read Receipts
1727
+ // ===========================================================================
1728
+ async sendReadReceipt(recipientTransportPubkey, messageEventId) {
1729
+ if (!this.keyManager) throw new Error("Not initialized");
1730
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1731
+ const event = NIP17.createReadReceipt(this.keyManager, nostrRecipient, messageEventId);
1732
+ await this.publishEvent(event);
1733
+ this.log("Sent read receipt for:", messageEventId, "to:", nostrRecipient.slice(0, 16));
1734
+ }
1735
+ onReadReceipt(handler) {
1736
+ this.readReceiptHandlers.add(handler);
1737
+ return () => this.readReceiptHandlers.delete(handler);
1738
+ }
1739
+ // ===========================================================================
1740
+ // Typing Indicators
1741
+ // ===========================================================================
1742
+ async sendTypingIndicator(recipientTransportPubkey) {
1743
+ if (!this.keyManager) throw new Error("Not initialized");
1744
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1745
+ const content = JSON.stringify({
1746
+ type: "typing",
1747
+ senderNametag: this.identity?.nametag
1748
+ });
1749
+ const event = NIP17.createGiftWrap(this.keyManager, nostrRecipient, content);
1750
+ await this.publishEvent(event);
1751
+ }
1752
+ onTypingIndicator(handler) {
1753
+ this.typingIndicatorHandlers.add(handler);
1754
+ return () => this.typingIndicatorHandlers.delete(handler);
1755
+ }
1684
1756
  /**
1685
1757
  * Resolve any identifier to full peer information.
1686
1758
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -2134,11 +2206,74 @@ var NostrTransportProvider = class {
2134
2206
  const pm = NIP17.unwrap(event, this.keyManager);
2135
2207
  this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
2136
2208
  if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
2137
- this.log("Skipping own message");
2209
+ try {
2210
+ const parsed = JSON.parse(pm.content);
2211
+ if (parsed?.selfWrap && parsed.recipientPubkey) {
2212
+ this.log("Self-wrap replay for recipient:", parsed.recipientPubkey?.slice(0, 16));
2213
+ const message2 = {
2214
+ id: parsed.originalId || pm.eventId,
2215
+ senderTransportPubkey: pm.senderPubkey,
2216
+ senderNametag: parsed.senderNametag,
2217
+ recipientTransportPubkey: parsed.recipientPubkey,
2218
+ content: parsed.text ?? "",
2219
+ timestamp: pm.timestamp * 1e3,
2220
+ encrypted: true,
2221
+ isSelfWrap: true
2222
+ };
2223
+ for (const handler of this.messageHandlers) {
2224
+ try {
2225
+ handler(message2);
2226
+ } catch (e) {
2227
+ this.log("Self-wrap handler error:", e);
2228
+ }
2229
+ }
2230
+ return;
2231
+ }
2232
+ } catch {
2233
+ }
2234
+ this.log("Skipping own non-self-wrap message");
2138
2235
  return;
2139
2236
  }
2140
- if (pm.kind !== EventKinds.CHAT_MESSAGE) {
2141
- this.log("Skipping non-chat message, kind:", pm.kind);
2237
+ if (isReadReceipt(pm)) {
2238
+ this.log("Read receipt from:", pm.senderPubkey?.slice(0, 16), "for:", pm.replyToEventId);
2239
+ if (pm.replyToEventId) {
2240
+ const receipt = {
2241
+ senderTransportPubkey: pm.senderPubkey,
2242
+ messageEventId: pm.replyToEventId,
2243
+ timestamp: pm.timestamp * 1e3
2244
+ };
2245
+ for (const handler of this.readReceiptHandlers) {
2246
+ try {
2247
+ handler(receipt);
2248
+ } catch (e) {
2249
+ this.log("Read receipt handler error:", e);
2250
+ }
2251
+ }
2252
+ }
2253
+ return;
2254
+ }
2255
+ try {
2256
+ const parsed = JSON.parse(pm.content);
2257
+ if (parsed?.type === "typing") {
2258
+ this.log("Typing indicator from:", pm.senderPubkey?.slice(0, 16));
2259
+ const indicator = {
2260
+ senderTransportPubkey: pm.senderPubkey,
2261
+ senderNametag: parsed.senderNametag,
2262
+ timestamp: pm.timestamp * 1e3
2263
+ };
2264
+ for (const handler of this.typingIndicatorHandlers) {
2265
+ try {
2266
+ handler(indicator);
2267
+ } catch (e) {
2268
+ this.log("Typing handler error:", e);
2269
+ }
2270
+ }
2271
+ return;
2272
+ }
2273
+ } catch {
2274
+ }
2275
+ if (!isChatMessage(pm)) {
2276
+ this.log("Skipping unknown message kind:", pm.kind);
2142
2277
  return;
2143
2278
  }
2144
2279
  let content = pm.content;
@@ -2153,7 +2288,9 @@ var NostrTransportProvider = class {
2153
2288
  }
2154
2289
  this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
2155
2290
  const message = {
2156
- id: pm.eventId,
2291
+ // Use outer gift wrap event.id so it matches the sender's stored giftWrap.id.
2292
+ // This ensures read receipts reference an ID the sender recognizes.
2293
+ id: event.id,
2157
2294
  senderTransportPubkey: pm.senderPubkey,
2158
2295
  senderNametag,
2159
2296
  content,