@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.
@@ -422,6 +422,27 @@ interface TransportProvider extends BaseProvider {
422
422
  * @returns Unsubscribe function
423
423
  */
424
424
  onPaymentRequestResponse?(handler: PaymentRequestResponseHandler): () => void;
425
+ /**
426
+ * Send a read receipt for a message
427
+ * @param recipientTransportPubkey - Transport pubkey of the message sender
428
+ * @param messageEventId - Event ID of the message being acknowledged
429
+ */
430
+ sendReadReceipt?(recipientTransportPubkey: string, messageEventId: string): Promise<void>;
431
+ /**
432
+ * Subscribe to incoming read receipts
433
+ * @returns Unsubscribe function
434
+ */
435
+ onReadReceipt?(handler: ReadReceiptHandler): () => void;
436
+ /**
437
+ * Send typing indicator to a recipient
438
+ * @param recipientTransportPubkey - Transport pubkey of the conversation partner
439
+ */
440
+ sendTypingIndicator?(recipientTransportPubkey: string): Promise<void>;
441
+ /**
442
+ * Subscribe to incoming typing indicators
443
+ * @returns Unsubscribe function
444
+ */
445
+ onTypingIndicator?(handler: TypingIndicatorHandler): () => void;
425
446
  /**
426
447
  * Get list of configured relay URLs
427
448
  */
@@ -511,6 +532,10 @@ interface IncomingMessage {
511
532
  content: string;
512
533
  timestamp: number;
513
534
  encrypted: boolean;
535
+ /** Set when this is a self-wrap replay (sent message recovered from relay) */
536
+ isSelfWrap?: boolean;
537
+ /** Recipient pubkey — only present on self-wrap replays */
538
+ recipientTransportPubkey?: string;
514
539
  }
515
540
  type MessageHandler = (message: IncomingMessage) => void;
516
541
  interface TokenTransferPayload {
@@ -632,6 +657,24 @@ interface PeerInfo {
632
657
  /** Event timestamp */
633
658
  timestamp: number;
634
659
  }
660
+ interface IncomingReadReceipt {
661
+ /** Transport-specific pubkey of the sender who read the message */
662
+ senderTransportPubkey: string;
663
+ /** Event ID of the message that was read */
664
+ messageEventId: string;
665
+ /** Timestamp */
666
+ timestamp: number;
667
+ }
668
+ type ReadReceiptHandler = (receipt: IncomingReadReceipt) => void;
669
+ interface IncomingTypingIndicator {
670
+ /** Transport-specific pubkey of the sender who is typing */
671
+ senderTransportPubkey: string;
672
+ /** Sender's nametag (if known) */
673
+ senderNametag?: string;
674
+ /** Timestamp */
675
+ timestamp: number;
676
+ }
677
+ type TypingIndicatorHandler = (indicator: IncomingTypingIndicator) => void;
635
678
 
636
679
  /**
637
680
  * WebSocket Abstraction
@@ -724,6 +767,8 @@ declare class NostrTransportProvider implements TransportProvider {
724
767
  private transferHandlers;
725
768
  private paymentRequestHandlers;
726
769
  private paymentRequestResponseHandlers;
770
+ private readReceiptHandlers;
771
+ private typingIndicatorHandlers;
727
772
  private broadcastHandlers;
728
773
  private eventCallbacks;
729
774
  constructor(config: NostrTransportProviderConfig);
@@ -773,6 +818,10 @@ declare class NostrTransportProvider implements TransportProvider {
773
818
  onPaymentRequest(handler: PaymentRequestHandler): () => void;
774
819
  sendPaymentRequestResponse(recipientPubkey: string, payload: PaymentRequestResponsePayload): Promise<string>;
775
820
  onPaymentRequestResponse(handler: PaymentRequestResponseHandler): () => void;
821
+ sendReadReceipt(recipientTransportPubkey: string, messageEventId: string): Promise<void>;
822
+ onReadReceipt(handler: ReadReceiptHandler): () => void;
823
+ sendTypingIndicator(recipientTransportPubkey: string): Promise<void>;
824
+ onTypingIndicator(handler: TypingIndicatorHandler): () => void;
776
825
  /**
777
826
  * Resolve any identifier to full peer information.
778
827
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -422,6 +422,27 @@ interface TransportProvider extends BaseProvider {
422
422
  * @returns Unsubscribe function
423
423
  */
424
424
  onPaymentRequestResponse?(handler: PaymentRequestResponseHandler): () => void;
425
+ /**
426
+ * Send a read receipt for a message
427
+ * @param recipientTransportPubkey - Transport pubkey of the message sender
428
+ * @param messageEventId - Event ID of the message being acknowledged
429
+ */
430
+ sendReadReceipt?(recipientTransportPubkey: string, messageEventId: string): Promise<void>;
431
+ /**
432
+ * Subscribe to incoming read receipts
433
+ * @returns Unsubscribe function
434
+ */
435
+ onReadReceipt?(handler: ReadReceiptHandler): () => void;
436
+ /**
437
+ * Send typing indicator to a recipient
438
+ * @param recipientTransportPubkey - Transport pubkey of the conversation partner
439
+ */
440
+ sendTypingIndicator?(recipientTransportPubkey: string): Promise<void>;
441
+ /**
442
+ * Subscribe to incoming typing indicators
443
+ * @returns Unsubscribe function
444
+ */
445
+ onTypingIndicator?(handler: TypingIndicatorHandler): () => void;
425
446
  /**
426
447
  * Get list of configured relay URLs
427
448
  */
@@ -511,6 +532,10 @@ interface IncomingMessage {
511
532
  content: string;
512
533
  timestamp: number;
513
534
  encrypted: boolean;
535
+ /** Set when this is a self-wrap replay (sent message recovered from relay) */
536
+ isSelfWrap?: boolean;
537
+ /** Recipient pubkey — only present on self-wrap replays */
538
+ recipientTransportPubkey?: string;
514
539
  }
515
540
  type MessageHandler = (message: IncomingMessage) => void;
516
541
  interface TokenTransferPayload {
@@ -632,6 +657,24 @@ interface PeerInfo {
632
657
  /** Event timestamp */
633
658
  timestamp: number;
634
659
  }
660
+ interface IncomingReadReceipt {
661
+ /** Transport-specific pubkey of the sender who read the message */
662
+ senderTransportPubkey: string;
663
+ /** Event ID of the message that was read */
664
+ messageEventId: string;
665
+ /** Timestamp */
666
+ timestamp: number;
667
+ }
668
+ type ReadReceiptHandler = (receipt: IncomingReadReceipt) => void;
669
+ interface IncomingTypingIndicator {
670
+ /** Transport-specific pubkey of the sender who is typing */
671
+ senderTransportPubkey: string;
672
+ /** Sender's nametag (if known) */
673
+ senderNametag?: string;
674
+ /** Timestamp */
675
+ timestamp: number;
676
+ }
677
+ type TypingIndicatorHandler = (indicator: IncomingTypingIndicator) => void;
635
678
 
636
679
  /**
637
680
  * WebSocket Abstraction
@@ -724,6 +767,8 @@ declare class NostrTransportProvider implements TransportProvider {
724
767
  private transferHandlers;
725
768
  private paymentRequestHandlers;
726
769
  private paymentRequestResponseHandlers;
770
+ private readReceiptHandlers;
771
+ private typingIndicatorHandlers;
727
772
  private broadcastHandlers;
728
773
  private eventCallbacks;
729
774
  constructor(config: NostrTransportProviderConfig);
@@ -773,6 +818,10 @@ declare class NostrTransportProvider implements TransportProvider {
773
818
  onPaymentRequest(handler: PaymentRequestHandler): () => void;
774
819
  sendPaymentRequestResponse(recipientPubkey: string, payload: PaymentRequestResponsePayload): Promise<string>;
775
820
  onPaymentRequestResponse(handler: PaymentRequestResponseHandler): () => void;
821
+ sendReadReceipt(recipientTransportPubkey: string, messageEventId: string): Promise<void>;
822
+ onReadReceipt(handler: ReadReceiptHandler): () => void;
823
+ sendTypingIndicator(recipientTransportPubkey: string): Promise<void>;
824
+ onTypingIndicator(handler: TypingIndicatorHandler): () => void;
776
825
  /**
777
826
  * Resolve any identifier to full peer information.
778
827
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -938,7 +938,9 @@ import {
938
938
  EventKinds,
939
939
  hashNametag,
940
940
  NostrClient,
941
- Filter
941
+ Filter,
942
+ isChatMessage,
943
+ isReadReceipt
942
944
  } from "@unicitylabs/nostr-js-sdk";
943
945
 
944
946
  // core/crypto.ts
@@ -1156,6 +1158,8 @@ var NostrTransportProvider = class {
1156
1158
  transferHandlers = /* @__PURE__ */ new Set();
1157
1159
  paymentRequestHandlers = /* @__PURE__ */ new Set();
1158
1160
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1161
+ readReceiptHandlers = /* @__PURE__ */ new Set();
1162
+ typingIndicatorHandlers = /* @__PURE__ */ new Set();
1159
1163
  broadcastHandlers = /* @__PURE__ */ new Map();
1160
1164
  eventCallbacks = /* @__PURE__ */ new Set();
1161
1165
  constructor(config) {
@@ -1407,6 +1411,18 @@ var NostrTransportProvider = class {
1407
1411
  const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1408
1412
  const giftWrap = NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
1409
1413
  await this.publishEvent(giftWrap);
1414
+ const selfWrapContent = JSON.stringify({
1415
+ selfWrap: true,
1416
+ originalId: giftWrap.id,
1417
+ recipientPubkey,
1418
+ senderNametag,
1419
+ text: content
1420
+ });
1421
+ const selfPubkey = this.keyManager.getPublicKeyHex();
1422
+ const selfGiftWrap = NIP17.createGiftWrap(this.keyManager, selfPubkey, selfWrapContent);
1423
+ this.publishEvent(selfGiftWrap).catch((err) => {
1424
+ this.log("Self-wrap publish failed:", err);
1425
+ });
1410
1426
  this.emitEvent({
1411
1427
  type: "message:sent",
1412
1428
  timestamp: Date.now(),
@@ -1505,6 +1521,37 @@ var NostrTransportProvider = class {
1505
1521
  this.paymentRequestResponseHandlers.add(handler);
1506
1522
  return () => this.paymentRequestResponseHandlers.delete(handler);
1507
1523
  }
1524
+ // ===========================================================================
1525
+ // Read Receipts
1526
+ // ===========================================================================
1527
+ async sendReadReceipt(recipientTransportPubkey, messageEventId) {
1528
+ if (!this.keyManager) throw new Error("Not initialized");
1529
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1530
+ const event = NIP17.createReadReceipt(this.keyManager, nostrRecipient, messageEventId);
1531
+ await this.publishEvent(event);
1532
+ this.log("Sent read receipt for:", messageEventId, "to:", nostrRecipient.slice(0, 16));
1533
+ }
1534
+ onReadReceipt(handler) {
1535
+ this.readReceiptHandlers.add(handler);
1536
+ return () => this.readReceiptHandlers.delete(handler);
1537
+ }
1538
+ // ===========================================================================
1539
+ // Typing Indicators
1540
+ // ===========================================================================
1541
+ async sendTypingIndicator(recipientTransportPubkey) {
1542
+ if (!this.keyManager) throw new Error("Not initialized");
1543
+ const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1544
+ const content = JSON.stringify({
1545
+ type: "typing",
1546
+ senderNametag: this.identity?.nametag
1547
+ });
1548
+ const event = NIP17.createGiftWrap(this.keyManager, nostrRecipient, content);
1549
+ await this.publishEvent(event);
1550
+ }
1551
+ onTypingIndicator(handler) {
1552
+ this.typingIndicatorHandlers.add(handler);
1553
+ return () => this.typingIndicatorHandlers.delete(handler);
1554
+ }
1508
1555
  /**
1509
1556
  * Resolve any identifier to full peer information.
1510
1557
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -1958,11 +2005,74 @@ var NostrTransportProvider = class {
1958
2005
  const pm = NIP17.unwrap(event, this.keyManager);
1959
2006
  this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
1960
2007
  if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
1961
- this.log("Skipping own message");
2008
+ try {
2009
+ const parsed = JSON.parse(pm.content);
2010
+ if (parsed?.selfWrap && parsed.recipientPubkey) {
2011
+ this.log("Self-wrap replay for recipient:", parsed.recipientPubkey?.slice(0, 16));
2012
+ const message2 = {
2013
+ id: parsed.originalId || pm.eventId,
2014
+ senderTransportPubkey: pm.senderPubkey,
2015
+ senderNametag: parsed.senderNametag,
2016
+ recipientTransportPubkey: parsed.recipientPubkey,
2017
+ content: parsed.text ?? "",
2018
+ timestamp: pm.timestamp * 1e3,
2019
+ encrypted: true,
2020
+ isSelfWrap: true
2021
+ };
2022
+ for (const handler of this.messageHandlers) {
2023
+ try {
2024
+ handler(message2);
2025
+ } catch (e) {
2026
+ this.log("Self-wrap handler error:", e);
2027
+ }
2028
+ }
2029
+ return;
2030
+ }
2031
+ } catch {
2032
+ }
2033
+ this.log("Skipping own non-self-wrap message");
1962
2034
  return;
1963
2035
  }
1964
- if (pm.kind !== EventKinds.CHAT_MESSAGE) {
1965
- this.log("Skipping non-chat message, kind:", pm.kind);
2036
+ if (isReadReceipt(pm)) {
2037
+ this.log("Read receipt from:", pm.senderPubkey?.slice(0, 16), "for:", pm.replyToEventId);
2038
+ if (pm.replyToEventId) {
2039
+ const receipt = {
2040
+ senderTransportPubkey: pm.senderPubkey,
2041
+ messageEventId: pm.replyToEventId,
2042
+ timestamp: pm.timestamp * 1e3
2043
+ };
2044
+ for (const handler of this.readReceiptHandlers) {
2045
+ try {
2046
+ handler(receipt);
2047
+ } catch (e) {
2048
+ this.log("Read receipt handler error:", e);
2049
+ }
2050
+ }
2051
+ }
2052
+ return;
2053
+ }
2054
+ try {
2055
+ const parsed = JSON.parse(pm.content);
2056
+ if (parsed?.type === "typing") {
2057
+ this.log("Typing indicator from:", pm.senderPubkey?.slice(0, 16));
2058
+ const indicator = {
2059
+ senderTransportPubkey: pm.senderPubkey,
2060
+ senderNametag: parsed.senderNametag,
2061
+ timestamp: pm.timestamp * 1e3
2062
+ };
2063
+ for (const handler of this.typingIndicatorHandlers) {
2064
+ try {
2065
+ handler(indicator);
2066
+ } catch (e) {
2067
+ this.log("Typing handler error:", e);
2068
+ }
2069
+ }
2070
+ return;
2071
+ }
2072
+ } catch {
2073
+ }
2074
+ if (!isChatMessage(pm)) {
2075
+ this.log("Skipping unknown message kind:", pm.kind);
1966
2076
  return;
1967
2077
  }
1968
2078
  let content = pm.content;
@@ -1977,7 +2087,9 @@ var NostrTransportProvider = class {
1977
2087
  }
1978
2088
  this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
1979
2089
  const message = {
1980
- id: pm.eventId,
2090
+ // Use outer gift wrap event.id so it matches the sender's stored giftWrap.id.
2091
+ // This ensures read receipts reference an ID the sender recognizes.
2092
+ id: event.id,
1981
2093
  senderTransportPubkey: pm.senderPubkey,
1982
2094
  senderNametag,
1983
2095
  content,