@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.
@@ -685,7 +685,7 @@ interface TrackedAddress extends TrackedAddressEntry {
685
685
  /** Primary nametag (from nametag cache, without @ prefix) */
686
686
  readonly nametag?: string;
687
687
  }
688
- type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:response' | 'message:dm' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:provider' | 'sync:error' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection';
688
+ type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:response' | 'message:dm' | 'message:read' | 'message:typing' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:provider' | 'sync:error' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection';
689
689
  interface SphereEventMap {
690
690
  'transfer:incoming': IncomingTransfer;
691
691
  'transfer:confirmed': TransferResult;
@@ -696,6 +696,15 @@ interface SphereEventMap {
696
696
  'payment_request:paid': IncomingPaymentRequest$1;
697
697
  'payment_request:response': PaymentRequestResponse;
698
698
  'message:dm': DirectMessage;
699
+ 'message:read': {
700
+ messageIds: string[];
701
+ peerPubkey: string;
702
+ };
703
+ 'message:typing': {
704
+ senderPubkey: string;
705
+ senderNametag?: string;
706
+ timestamp: number;
707
+ };
699
708
  'message:broadcast': BroadcastMessage;
700
709
  'sync:started': {
701
710
  source: string;
@@ -1023,6 +1032,27 @@ interface TransportProvider extends BaseProvider {
1023
1032
  * @returns Unsubscribe function
1024
1033
  */
1025
1034
  onPaymentRequestResponse?(handler: PaymentRequestResponseHandler): () => void;
1035
+ /**
1036
+ * Send a read receipt for a message
1037
+ * @param recipientTransportPubkey - Transport pubkey of the message sender
1038
+ * @param messageEventId - Event ID of the message being acknowledged
1039
+ */
1040
+ sendReadReceipt?(recipientTransportPubkey: string, messageEventId: string): Promise<void>;
1041
+ /**
1042
+ * Subscribe to incoming read receipts
1043
+ * @returns Unsubscribe function
1044
+ */
1045
+ onReadReceipt?(handler: ReadReceiptHandler): () => void;
1046
+ /**
1047
+ * Send typing indicator to a recipient
1048
+ * @param recipientTransportPubkey - Transport pubkey of the conversation partner
1049
+ */
1050
+ sendTypingIndicator?(recipientTransportPubkey: string): Promise<void>;
1051
+ /**
1052
+ * Subscribe to incoming typing indicators
1053
+ * @returns Unsubscribe function
1054
+ */
1055
+ onTypingIndicator?(handler: TypingIndicatorHandler): () => void;
1026
1056
  /**
1027
1057
  * Get list of configured relay URLs
1028
1058
  */
@@ -1112,6 +1142,10 @@ interface IncomingMessage {
1112
1142
  content: string;
1113
1143
  timestamp: number;
1114
1144
  encrypted: boolean;
1145
+ /** Set when this is a self-wrap replay (sent message recovered from relay) */
1146
+ isSelfWrap?: boolean;
1147
+ /** Recipient pubkey — only present on self-wrap replays */
1148
+ recipientTransportPubkey?: string;
1115
1149
  }
1116
1150
  type MessageHandler = (message: IncomingMessage) => void;
1117
1151
  interface TokenTransferPayload {
@@ -1225,6 +1259,24 @@ interface PeerInfo {
1225
1259
  /** Event timestamp */
1226
1260
  timestamp: number;
1227
1261
  }
1262
+ interface IncomingReadReceipt {
1263
+ /** Transport-specific pubkey of the sender who read the message */
1264
+ senderTransportPubkey: string;
1265
+ /** Event ID of the message that was read */
1266
+ messageEventId: string;
1267
+ /** Timestamp */
1268
+ timestamp: number;
1269
+ }
1270
+ type ReadReceiptHandler = (receipt: IncomingReadReceipt) => void;
1271
+ interface IncomingTypingIndicator {
1272
+ /** Transport-specific pubkey of the sender who is typing */
1273
+ senderTransportPubkey: string;
1274
+ /** Sender's nametag (if known) */
1275
+ senderNametag?: string;
1276
+ /** Timestamp */
1277
+ timestamp: number;
1278
+ }
1279
+ type TypingIndicatorHandler = (indicator: IncomingTypingIndicator) => void;
1228
1280
 
1229
1281
  /**
1230
1282
  * L1 Payments Sub-Module
@@ -2556,6 +2608,10 @@ declare class CommunicationsModule {
2556
2608
  * Get unread count
2557
2609
  */
2558
2610
  getUnreadCount(peerPubkey?: string): number;
2611
+ /**
2612
+ * Send typing indicator to a peer
2613
+ */
2614
+ sendTypingIndicator(peerPubkey: string): Promise<void>;
2559
2615
  /**
2560
2616
  * Subscribe to incoming DMs
2561
2617
  */
@@ -685,7 +685,7 @@ interface TrackedAddress extends TrackedAddressEntry {
685
685
  /** Primary nametag (from nametag cache, without @ prefix) */
686
686
  readonly nametag?: string;
687
687
  }
688
- type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:response' | 'message:dm' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:provider' | 'sync:error' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection';
688
+ type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:response' | 'message:dm' | 'message:read' | 'message:typing' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:provider' | 'sync:error' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection';
689
689
  interface SphereEventMap {
690
690
  'transfer:incoming': IncomingTransfer;
691
691
  'transfer:confirmed': TransferResult;
@@ -696,6 +696,15 @@ interface SphereEventMap {
696
696
  'payment_request:paid': IncomingPaymentRequest$1;
697
697
  'payment_request:response': PaymentRequestResponse;
698
698
  'message:dm': DirectMessage;
699
+ 'message:read': {
700
+ messageIds: string[];
701
+ peerPubkey: string;
702
+ };
703
+ 'message:typing': {
704
+ senderPubkey: string;
705
+ senderNametag?: string;
706
+ timestamp: number;
707
+ };
699
708
  'message:broadcast': BroadcastMessage;
700
709
  'sync:started': {
701
710
  source: string;
@@ -1023,6 +1032,27 @@ interface TransportProvider extends BaseProvider {
1023
1032
  * @returns Unsubscribe function
1024
1033
  */
1025
1034
  onPaymentRequestResponse?(handler: PaymentRequestResponseHandler): () => void;
1035
+ /**
1036
+ * Send a read receipt for a message
1037
+ * @param recipientTransportPubkey - Transport pubkey of the message sender
1038
+ * @param messageEventId - Event ID of the message being acknowledged
1039
+ */
1040
+ sendReadReceipt?(recipientTransportPubkey: string, messageEventId: string): Promise<void>;
1041
+ /**
1042
+ * Subscribe to incoming read receipts
1043
+ * @returns Unsubscribe function
1044
+ */
1045
+ onReadReceipt?(handler: ReadReceiptHandler): () => void;
1046
+ /**
1047
+ * Send typing indicator to a recipient
1048
+ * @param recipientTransportPubkey - Transport pubkey of the conversation partner
1049
+ */
1050
+ sendTypingIndicator?(recipientTransportPubkey: string): Promise<void>;
1051
+ /**
1052
+ * Subscribe to incoming typing indicators
1053
+ * @returns Unsubscribe function
1054
+ */
1055
+ onTypingIndicator?(handler: TypingIndicatorHandler): () => void;
1026
1056
  /**
1027
1057
  * Get list of configured relay URLs
1028
1058
  */
@@ -1112,6 +1142,10 @@ interface IncomingMessage {
1112
1142
  content: string;
1113
1143
  timestamp: number;
1114
1144
  encrypted: boolean;
1145
+ /** Set when this is a self-wrap replay (sent message recovered from relay) */
1146
+ isSelfWrap?: boolean;
1147
+ /** Recipient pubkey — only present on self-wrap replays */
1148
+ recipientTransportPubkey?: string;
1115
1149
  }
1116
1150
  type MessageHandler = (message: IncomingMessage) => void;
1117
1151
  interface TokenTransferPayload {
@@ -1225,6 +1259,24 @@ interface PeerInfo {
1225
1259
  /** Event timestamp */
1226
1260
  timestamp: number;
1227
1261
  }
1262
+ interface IncomingReadReceipt {
1263
+ /** Transport-specific pubkey of the sender who read the message */
1264
+ senderTransportPubkey: string;
1265
+ /** Event ID of the message that was read */
1266
+ messageEventId: string;
1267
+ /** Timestamp */
1268
+ timestamp: number;
1269
+ }
1270
+ type ReadReceiptHandler = (receipt: IncomingReadReceipt) => void;
1271
+ interface IncomingTypingIndicator {
1272
+ /** Transport-specific pubkey of the sender who is typing */
1273
+ senderTransportPubkey: string;
1274
+ /** Sender's nametag (if known) */
1275
+ senderNametag?: string;
1276
+ /** Timestamp */
1277
+ timestamp: number;
1278
+ }
1279
+ type TypingIndicatorHandler = (indicator: IncomingTypingIndicator) => void;
1228
1280
 
1229
1281
  /**
1230
1282
  * L1 Payments Sub-Module
@@ -2556,6 +2608,10 @@ declare class CommunicationsModule {
2556
2608
  * Get unread count
2557
2609
  */
2558
2610
  getUnreadCount(peerPubkey?: string): number;
2611
+ /**
2612
+ * Send typing indicator to a peer
2613
+ */
2614
+ sendTypingIndicator(peerPubkey: string): Promise<void>;
2559
2615
  /**
2560
2616
  * Subscribe to incoming DMs
2561
2617
  */
@@ -7258,6 +7258,28 @@ var CommunicationsModule = class {
7258
7258
  this.unsubscribeMessages = deps.transport.onMessage((msg) => {
7259
7259
  this.handleIncomingMessage(msg);
7260
7260
  });
7261
+ if (deps.transport.onReadReceipt) {
7262
+ deps.transport.onReadReceipt((receipt) => {
7263
+ const msg = this.messages.get(receipt.messageEventId);
7264
+ if (msg && msg.senderPubkey === this.deps.identity.chainPubkey) {
7265
+ msg.isRead = true;
7266
+ this.save();
7267
+ this.deps.emitEvent("message:read", {
7268
+ messageIds: [receipt.messageEventId],
7269
+ peerPubkey: receipt.senderTransportPubkey
7270
+ });
7271
+ }
7272
+ });
7273
+ }
7274
+ if (deps.transport.onTypingIndicator) {
7275
+ deps.transport.onTypingIndicator((indicator) => {
7276
+ this.deps.emitEvent("message:typing", {
7277
+ senderPubkey: indicator.senderTransportPubkey,
7278
+ senderNametag: indicator.senderNametag,
7279
+ timestamp: indicator.timestamp
7280
+ });
7281
+ });
7282
+ }
7261
7283
  }
7262
7284
  /**
7263
7285
  * Load messages from storage
@@ -7301,7 +7323,7 @@ var CommunicationsModule = class {
7301
7323
  recipientPubkey,
7302
7324
  content,
7303
7325
  timestamp: Date.now(),
7304
- isRead: true
7326
+ isRead: false
7305
7327
  };
7306
7328
  this.messages.set(message.id, message);
7307
7329
  if (this.config.autoSave) {
@@ -7347,6 +7369,16 @@ var CommunicationsModule = class {
7347
7369
  if (this.config.autoSave) {
7348
7370
  await this.save();
7349
7371
  }
7372
+ if (this.config.readReceipts && this.deps?.transport.sendReadReceipt) {
7373
+ for (const id of messageIds) {
7374
+ const msg = this.messages.get(id);
7375
+ if (msg && msg.senderPubkey !== this.deps.identity.chainPubkey) {
7376
+ this.deps.transport.sendReadReceipt(msg.senderPubkey, id).catch((err) => {
7377
+ console.warn("[Communications] Failed to send read receipt:", err);
7378
+ });
7379
+ }
7380
+ }
7381
+ }
7350
7382
  }
7351
7383
  /**
7352
7384
  * Get unread count
@@ -7360,6 +7392,15 @@ var CommunicationsModule = class {
7360
7392
  }
7361
7393
  return messages.length;
7362
7394
  }
7395
+ /**
7396
+ * Send typing indicator to a peer
7397
+ */
7398
+ async sendTypingIndicator(peerPubkey) {
7399
+ this.ensureInitialized();
7400
+ if (this.deps.transport.sendTypingIndicator) {
7401
+ await this.deps.transport.sendTypingIndicator(peerPubkey);
7402
+ }
7403
+ }
7363
7404
  /**
7364
7405
  * Subscribe to incoming DMs
7365
7406
  */
@@ -7429,7 +7470,26 @@ var CommunicationsModule = class {
7429
7470
  // Private: Message Handling
7430
7471
  // ===========================================================================
7431
7472
  handleIncomingMessage(msg) {
7473
+ if (msg.isSelfWrap && msg.recipientTransportPubkey) {
7474
+ if (this.messages.has(msg.id)) return;
7475
+ const message2 = {
7476
+ id: msg.id,
7477
+ senderPubkey: this.deps.identity.chainPubkey,
7478
+ senderNametag: msg.senderNametag,
7479
+ recipientPubkey: msg.recipientTransportPubkey,
7480
+ content: msg.content,
7481
+ timestamp: msg.timestamp,
7482
+ isRead: false
7483
+ };
7484
+ this.messages.set(message2.id, message2);
7485
+ this.deps.emitEvent("message:dm", message2);
7486
+ if (this.config.autoSave) {
7487
+ this.save();
7488
+ }
7489
+ return;
7490
+ }
7432
7491
  if (msg.senderTransportPubkey === this.deps?.identity.chainPubkey) return;
7492
+ if (this.messages.has(msg.id)) return;
7433
7493
  const message = {
7434
7494
  id: msg.id,
7435
7495
  senderPubkey: msg.senderTransportPubkey,