@bsv/message-box-client 1.2.4 → 1.2.5

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 (30) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/MessageBoxClient.js +26 -24
  3. package/dist/cjs/src/MessageBoxClient.js.map +1 -1
  4. package/dist/cjs/src/PeerPayClient.js +8 -5
  5. package/dist/cjs/src/PeerPayClient.js.map +1 -1
  6. package/dist/cjs/src/__tests/MessageBoxClient.test.js +0 -15
  7. package/dist/cjs/src/__tests/MessageBoxClient.test.js.map +1 -1
  8. package/dist/cjs/src/__tests/PeerPayClientUnit.test.js +2 -2
  9. package/dist/cjs/src/__tests/PeerPayClientUnit.test.js.map +1 -1
  10. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/dist/esm/src/MessageBoxClient.js +26 -24
  12. package/dist/esm/src/MessageBoxClient.js.map +1 -1
  13. package/dist/esm/src/PeerPayClient.js +8 -5
  14. package/dist/esm/src/PeerPayClient.js.map +1 -1
  15. package/dist/esm/src/__tests/MessageBoxClient.test.js +0 -15
  16. package/dist/esm/src/__tests/MessageBoxClient.test.js.map +1 -1
  17. package/dist/esm/src/__tests/PeerPayClientUnit.test.js +2 -2
  18. package/dist/esm/src/__tests/PeerPayClientUnit.test.js.map +1 -1
  19. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  20. package/dist/types/src/MessageBoxClient.d.ts +4 -3
  21. package/dist/types/src/MessageBoxClient.d.ts.map +1 -1
  22. package/dist/types/src/PeerPayClient.d.ts +6 -3
  23. package/dist/types/src/PeerPayClient.d.ts.map +1 -1
  24. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  25. package/dist/umd/bundle.js +1 -1
  26. package/package.json +1 -1
  27. package/src/MessageBoxClient.ts +88 -85
  28. package/src/PeerPayClient.ts +21 -14
  29. package/src/__tests/MessageBoxClient.test.ts +0 -23
  30. package/src/__tests/PeerPayClientUnit.test.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/message-box-client",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -116,7 +116,7 @@ export class MessageBoxClient {
116
116
  * })
117
117
  * await client.init()
118
118
  */
119
- constructor(options: MessageBoxClientOptions = {}) {
119
+ constructor (options: MessageBoxClientOptions = {}) {
120
120
  const {
121
121
  host,
122
122
  walletClient,
@@ -168,7 +168,7 @@ export class MessageBoxClient {
168
168
  * await client.init()
169
169
  * await client.sendMessage({ recipient, messageBox: 'inbox', body: 'Hello' })
170
170
  */
171
- async init(targetHost: string = this.host, originator?: string): Promise<void> {
171
+ async init (targetHost: string = this.host, originator?: string): Promise<void> {
172
172
  const normalizedHost = targetHost?.trim()
173
173
  if (normalizedHost === '') {
174
174
  throw new Error('Cannot anoint host: No valid host provided')
@@ -189,9 +189,14 @@ export class MessageBoxClient {
189
189
  // 3. If none our found, anoint this host
190
190
  if (firstAdvertisement == null || firstAdvertisement?.host?.trim() === '' || firstAdvertisement?.host !== normalizedHost) {
191
191
  Logger.log('[MB CLIENT] Anointing host:', normalizedHost)
192
- const { txid } = await this.anointHost(normalizedHost, originator)
193
- if (txid == null || txid.trim() === '') {
194
- throw new Error('Failed to anoint host: No transaction ID returned')
192
+ try {
193
+ const { txid } = await this.anointHost(normalizedHost, originator)
194
+ if (txid == null || txid.trim() === '') {
195
+ throw new Error('Failed to anoint host: No transaction ID returned')
196
+ }
197
+ } catch (error) {
198
+ Logger.log('[MB CLIENT] Failed to anoint host, continuing with default functionality:', error)
199
+ // Continue with default host - client can still function for basic operations
195
200
  }
196
201
  }
197
202
  this.initialized = true
@@ -208,7 +213,7 @@ export class MessageBoxClient {
208
213
  *
209
214
  * Used automatically by all public methods that require initialization.
210
215
  */
211
- private async assertInitialized(): Promise<void> {
216
+ private async assertInitialized (): Promise<void> {
212
217
  if (!this.initialized || this.host == null || this.host.trim() === '') {
213
218
  await this.init()
214
219
  }
@@ -221,7 +226,7 @@ export class MessageBoxClient {
221
226
  * Returns a live list of WebSocket rooms the client is subscribed to.
222
227
  * Useful for inspecting state or ensuring no duplicates are joined.
223
228
  */
224
- public getJoinedRooms(): Set<string> {
229
+ public getJoinedRooms (): Set<string> {
225
230
  return this.joinedRooms
226
231
  }
227
232
 
@@ -233,7 +238,7 @@ export class MessageBoxClient {
233
238
  * Returns the client's identity key, used for signing, encryption, and addressing.
234
239
  * If not already loaded, it will fetch and cache it.
235
240
  */
236
- public async getIdentityKey(originator?: string): Promise<string> {
241
+ public async getIdentityKey (originator?: string): Promise<string> {
237
242
  if (this.myIdentityKey != null && this.myIdentityKey.trim() !== '') {
238
243
  return this.myIdentityKey
239
244
  }
@@ -261,7 +266,7 @@ export class MessageBoxClient {
261
266
  * Note: Do not interact with the socket directly unless necessary.
262
267
  * Use the provided `sendLiveMessage`, `listenForLiveMessages`, and related methods.
263
268
  */
264
- public get testSocket(): ReturnType<typeof AuthSocketClient> | undefined {
269
+ public get testSocket (): ReturnType<typeof AuthSocketClient> | undefined {
265
270
  return this.socket
266
271
  }
267
272
 
@@ -289,8 +294,7 @@ export class MessageBoxClient {
289
294
  * await mb.initializeConnection()
290
295
  * // WebSocket is now ready for use
291
296
  */
292
- async initializeConnection(originator?: string): Promise<void> {
293
- await this.assertInitialized()
297
+ async initializeConnection (originator?: string, overrideHost?: string): Promise<void> {
294
298
  Logger.log('[MB CLIENT] initializeConnection() STARTED')
295
299
 
296
300
  if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
@@ -305,10 +309,11 @@ export class MessageBoxClient {
305
309
  Logger.log('[MB CLIENT] Setting up WebSocket connection...')
306
310
 
307
311
  if (this.socket == null) {
308
- if (typeof this.host !== 'string' || this.host.trim() === '') {
309
- throw new Error('Cannot initialize WebSocket: Host is not set')
312
+ const targetHost = overrideHost ?? this.host
313
+ if (typeof targetHost !== 'string' || targetHost.trim() === '') {
314
+ throw new Error('Cannot initialize WebSocket: No valid host provided')
310
315
  }
311
- this.socket = AuthSocketClient(this.host, { wallet: this.walletClient })
316
+ this.socket = AuthSocketClient(targetHost, { wallet: this.walletClient })
312
317
 
313
318
  let identitySent = false
314
319
  let authenticated = false
@@ -383,7 +388,7 @@ export class MessageBoxClient {
383
388
  * @example
384
389
  * const host = await resolveHostForRecipient('028d...') // → returns either overlay host or this.host
385
390
  */
386
- async resolveHostForRecipient(identityKey: string, originator?: string): Promise<string> {
391
+ async resolveHostForRecipient (identityKey: string, originator?: string): Promise<string> {
387
392
  const advertisementTokens = await this.queryAdvertisements(identityKey, undefined, originator)
388
393
  if (advertisementTokens.length === 0) {
389
394
  Logger.warn(`[MB CLIENT] No advertisements for ${identityKey}, using default host ${this.host}`)
@@ -401,7 +406,7 @@ export class MessageBoxClient {
401
406
  * @param host? if passed, only look for adverts anointed at that host
402
407
  * @returns 0-length array if nothing valid was found
403
408
  */
404
- async queryAdvertisements(
409
+ async queryAdvertisements (
405
410
  identityKey?: string,
406
411
  host?: string,
407
412
  originator?: string
@@ -466,14 +471,13 @@ export class MessageBoxClient {
466
471
  * await client.joinRoom('payment_inbox')
467
472
  * // Now listening for real-time messages in room '028d...-payment_inbox'
468
473
  */
469
- async joinRoom(messageBox: string): Promise<void> {
470
- await this.assertInitialized()
474
+ async joinRoom (messageBox: string, originator?: string, overrideHost?: string): Promise<void> {
471
475
  Logger.log(`[MB CLIENT] Attempting to join WebSocket room: ${messageBox}`)
472
476
 
473
477
  // Ensure WebSocket connection is established first
474
478
  if (this.socket == null) {
475
479
  Logger.log('[MB CLIENT] No WebSocket connection. Initializing...')
476
- await this.initializeConnection()
480
+ await this.initializeConnection(originator, overrideHost)
477
481
  }
478
482
 
479
483
  if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
@@ -525,20 +529,27 @@ export class MessageBoxClient {
525
529
  * onMessage: (msg) => console.log('Received live message:', msg)
526
530
  * })
527
531
  */
528
- async listenForLiveMessages({
532
+ async listenForLiveMessages ({
529
533
  onMessage,
530
534
  messageBox,
531
- originator
535
+ originator,
536
+ overrideHost
532
537
  }: {
533
538
  onMessage: (message: PeerMessage) => void
534
539
  messageBox: string
535
540
  originator?: string
541
+ overrideHost?: string
536
542
  }): Promise<void> {
537
- await this.assertInitialized()
538
543
  Logger.log(`[MB CLIENT] Setting up listener for WebSocket room: ${messageBox}`)
539
544
 
540
- // Ensure WebSocket connection and room join
541
- await this.joinRoom(messageBox)
545
+ // Ensure WebSocket connection is established first
546
+ if (this.socket == null) {
547
+ Logger.log('[MB CLIENT] No WebSocket connection. Initializing...')
548
+ await this.initializeConnection(originator, overrideHost)
549
+ }
550
+
551
+ // Join the room
552
+ await this.joinRoom(messageBox, originator, overrideHost)
542
553
 
543
554
  // Ensure identity key is available before creating roomId
544
555
  if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
@@ -623,7 +634,7 @@ export class MessageBoxClient {
623
634
  * body: { amount: 1000 }
624
635
  * })
625
636
  */
626
- async sendLiveMessage({
637
+ async sendLiveMessage ({
627
638
  recipient,
628
639
  messageBox,
629
640
  body,
@@ -781,7 +792,7 @@ export class MessageBoxClient {
781
792
  * @example
782
793
  * await client.leaveRoom('payment_inbox')
783
794
  */
784
- async leaveRoom(messageBox: string): Promise<void> {
795
+ async leaveRoom (messageBox: string): Promise<void> {
785
796
  await this.assertInitialized()
786
797
  if (this.socket == null) {
787
798
  Logger.warn('[MB CLIENT] Attempted to leave a room but WebSocket is not connected.')
@@ -813,7 +824,7 @@ export class MessageBoxClient {
813
824
  * @example
814
825
  * await client.disconnectWebSocket()
815
826
  */
816
- async disconnectWebSocket(): Promise<void> {
827
+ async disconnectWebSocket (): Promise<void> {
817
828
  await this.assertInitialized()
818
829
  if (this.socket != null) {
819
830
  Logger.log('[MB CLIENT] Closing WebSocket connection...')
@@ -851,7 +862,7 @@ export class MessageBoxClient {
851
862
  * body: { type: 'ping' }
852
863
  * })
853
864
  */
854
- async sendMessage(
865
+ async sendMessage (
855
866
  message: SendMessageParams,
856
867
  overrideHost?: string,
857
868
  originator?: string
@@ -1015,7 +1026,7 @@ export class MessageBoxClient {
1015
1026
  * @example
1016
1027
  * const { txid } = await client.anointHost('https://my-messagebox.io')
1017
1028
  */
1018
- async anointHost(host: string, originator?: string): Promise<{ txid: string }> {
1029
+ async anointHost (host: string, originator?: string): Promise<{ txid: string }> {
1019
1030
  Logger.log('[MB CLIENT] Starting anointHost...')
1020
1031
  try {
1021
1032
  if (!host.startsWith('http')) {
@@ -1097,7 +1108,7 @@ export class MessageBoxClient {
1097
1108
  * @example
1098
1109
  * const { txid } = await client.revokeHost('https://my-messagebox.io')
1099
1110
  */
1100
- async revokeHostAdvertisement(advertisementToken: AdvertisementToken, originator?: string): Promise<{ txid: string }> {
1111
+ async revokeHostAdvertisement (advertisementToken: AdvertisementToken, originator?: string): Promise<{ txid: string }> {
1101
1112
  Logger.log('[MB CLIENT] Starting revokeHost...')
1102
1113
  const outpoint = `${advertisementToken.txid}.${advertisementToken.outputIndex}`
1103
1114
  try {
@@ -1213,7 +1224,11 @@ export class MessageBoxClient {
1213
1224
 
1214
1225
  let hosts: string[] = host != null ? [host] : []
1215
1226
  if (hosts.length === 0) {
1216
- const advertisedHosts = await this.queryAdvertisements(await this.getIdentityKey(originator), originator)
1227
+ const advertisedHosts = await this.queryAdvertisements(
1228
+ await this.getIdentityKey(originator),
1229
+ undefined,
1230
+ originator
1231
+ )
1217
1232
  hosts = Array.from(new Set([this.host, ...advertisedHosts.map(h => h.host)]))
1218
1233
  }
1219
1234
 
@@ -1340,7 +1355,7 @@ export class MessageBoxClient {
1340
1355
  if (
1341
1356
  messageContent != null &&
1342
1357
  typeof messageContent === 'object' &&
1343
- typeof (messageContent as any).encryptedMessage === 'string'
1358
+ typeof (messageContent).encryptedMessage === 'string'
1344
1359
  ) {
1345
1360
  Logger.log(
1346
1361
  `[MB CLIENT] Decrypting message from ${String(message.sender)}…`
@@ -1522,7 +1537,7 @@ export class MessageBoxClient {
1522
1537
  * const success = await client.acknowledgeNotification(message)
1523
1538
  * console.log(success ? 'Payment received' : 'No payment or failed')
1524
1539
  */
1525
- async acknowledgeNotification(message: PeerMessage): Promise<boolean> {
1540
+ async acknowledgeNotification (message: PeerMessage): Promise<boolean> {
1526
1541
  await this.acknowledgeMessage({ messageIds: [message.messageId] })
1527
1542
 
1528
1543
  const parsedBody: unknown =
@@ -1611,8 +1626,7 @@ export class MessageBoxClient {
1611
1626
  * @example
1612
1627
  * await client.acknowledgeMessage({ messageIds: ['msg123', 'msg456'] })
1613
1628
  */
1614
- async acknowledgeMessage({ messageIds, host, originator }: AcknowledgeMessageParams): Promise<string> {
1615
- await this.assertInitialized()
1629
+ async acknowledgeMessage ({ messageIds, host, originator }: AcknowledgeMessageParams): Promise<string> {
1616
1630
  if (!Array.isArray(messageIds) || messageIds.length === 0) {
1617
1631
  throw new Error('Message IDs array cannot be empty')
1618
1632
  }
@@ -1694,11 +1708,10 @@ export class MessageBoxClient {
1694
1708
  * recipientFee: -1
1695
1709
  * })
1696
1710
  */
1697
- async setMessageBoxPermission(
1711
+ async setMessageBoxPermission (
1698
1712
  params: SetMessageBoxPermissionParams,
1699
1713
  overrideHost?: string
1700
1714
  ): Promise<void> {
1701
- await this.assertInitialized()
1702
1715
  const finalHost = overrideHost ?? this.host
1703
1716
 
1704
1717
  Logger.log('[MB CLIENT] Setting messageBox permission...')
@@ -1742,12 +1755,10 @@ export class MessageBoxClient {
1742
1755
  * sender: '03abc123...'
1743
1756
  * })
1744
1757
  */
1745
- async getMessageBoxPermission(
1758
+ async getMessageBoxPermission (
1746
1759
  params: GetMessageBoxPermissionParams,
1747
1760
  overrideHost?: string
1748
1761
  ): Promise<MessageBoxPermission | null> {
1749
- await this.assertInitialized()
1750
-
1751
1762
  const finalHost = overrideHost ?? await this.resolveHostForRecipient(params.recipient)
1752
1763
  const queryParams = new URLSearchParams({
1753
1764
  recipient: params.recipient,
@@ -1789,9 +1800,7 @@ export class MessageBoxClient {
1789
1800
  * messageBox: 'notifications'
1790
1801
  * })
1791
1802
  */
1792
- async getMessageBoxQuote(params: GetQuoteParams, overrideHost?: string): Promise<MessageBoxQuote> {
1793
- await this.assertInitialized()
1794
-
1803
+ async getMessageBoxQuote (params: GetQuoteParams, overrideHost?: string): Promise<MessageBoxQuote> {
1795
1804
  const finalHost = overrideHost ?? await this.resolveHostForRecipient(params.recipient)
1796
1805
  const queryParams = new URLSearchParams({
1797
1806
  recipient: params.recipient,
@@ -1847,9 +1856,7 @@ export class MessageBoxClient {
1847
1856
  * offset: 0
1848
1857
  * })
1849
1858
  */
1850
- async listMessageBoxPermissions(params?: ListPermissionsParams, overrideHost?: string): Promise<MessageBoxPermission[]> {
1851
- await this.assertInitialized()
1852
-
1859
+ async listMessageBoxPermissions (params?: ListPermissionsParams, overrideHost?: string): Promise<MessageBoxPermission[]> {
1853
1860
  const finalHost = overrideHost ?? this.host
1854
1861
  const queryParams = new URLSearchParams()
1855
1862
 
@@ -1908,7 +1915,7 @@ export class MessageBoxClient {
1908
1915
  * await client.allowNotificationsFromPeer('03abc123...') // Always allow
1909
1916
  * await client.allowNotificationsFromPeer('03def456...', 5) // Allow for 5 sats
1910
1917
  */
1911
- async allowNotificationsFromPeer(identityKey: PubKeyHex, recipientFee: number = 0, overrideHost?: string): Promise<void> {
1918
+ async allowNotificationsFromPeer (identityKey: PubKeyHex, recipientFee: number = 0, overrideHost?: string): Promise<void> {
1912
1919
  await this.setMessageBoxPermission({
1913
1920
  messageBox: 'notifications',
1914
1921
  sender: identityKey,
@@ -1928,7 +1935,7 @@ export class MessageBoxClient {
1928
1935
  * @example
1929
1936
  * await client.denyNotificationsFromPeer('03spam123...')
1930
1937
  */
1931
- async denyNotificationsFromPeer(identityKey: PubKeyHex, overrideHost?: string): Promise<void> {
1938
+ async denyNotificationsFromPeer (identityKey: PubKeyHex, overrideHost?: string): Promise<void> {
1932
1939
  await this.setMessageBoxPermission({
1933
1940
  messageBox: 'notifications',
1934
1941
  sender: identityKey,
@@ -1949,7 +1956,7 @@ export class MessageBoxClient {
1949
1956
  * const status = await client.checkPeerNotificationStatus('03abc123...')
1950
1957
  * console.log(status.allowed) // true/false
1951
1958
  */
1952
- async checkPeerNotificationStatus(identityKey: PubKeyHex, overrideHost?: string): Promise<MessageBoxPermission | null> {
1959
+ async checkPeerNotificationStatus (identityKey: PubKeyHex, overrideHost?: string): Promise<MessageBoxPermission | null> {
1953
1960
  const myIdentityKey = await this.getIdentityKey()
1954
1961
  return await this.getMessageBoxPermission({
1955
1962
  recipient: myIdentityKey,
@@ -1969,7 +1976,7 @@ export class MessageBoxClient {
1969
1976
  * @example
1970
1977
  * const notifications = await client.listPeerNotifications()
1971
1978
  */
1972
- async listPeerNotifications(overrideHost?: string): Promise<MessageBoxPermission[]> {
1979
+ async listPeerNotifications (overrideHost?: string): Promise<MessageBoxPermission[]> {
1973
1980
  return await this.listMessageBoxPermissions({ messageBox: 'notifications' }, overrideHost)
1974
1981
  }
1975
1982
 
@@ -1992,7 +1999,7 @@ export class MessageBoxClient {
1992
1999
  * // Send with maximum payment limit for safety
1993
2000
  * await client.sendNotification('03def456...', { title: 'Alert', body: 'Important update' }, 50)
1994
2001
  */
1995
- async sendNotification(
2002
+ async sendNotification (
1996
2003
  recipient: PubKeyHex,
1997
2004
  body: string | object,
1998
2005
  overrideHost?: string
@@ -2028,12 +2035,10 @@ export class MessageBoxClient {
2028
2035
  * deviceId: 'iPhone15Pro'
2029
2036
  * })
2030
2037
  */
2031
- async registerDevice(
2038
+ async registerDevice (
2032
2039
  params: DeviceRegistrationParams,
2033
2040
  overrideHost?: string
2034
2041
  ): Promise<DeviceRegistrationResponse> {
2035
- await this.assertInitialized()
2036
-
2037
2042
  if (params.fcmToken == null || params.fcmToken.trim() === '') {
2038
2043
  throw new Error('fcmToken is required and must be a non-empty string')
2039
2044
  }
@@ -2095,11 +2100,9 @@ export class MessageBoxClient {
2095
2100
  * console.log(`Device: ${device.platform} - ${device.fcmToken}`)
2096
2101
  * })
2097
2102
  */
2098
- async listRegisteredDevices(
2103
+ async listRegisteredDevices (
2099
2104
  overrideHost?: string
2100
2105
  ): Promise<RegisteredDevice[]> {
2101
- await this.assertInitialized()
2102
-
2103
2106
  const finalHost = overrideHost ?? this.host
2104
2107
 
2105
2108
  Logger.log('[MB CLIENT] Listing registered devices...')
@@ -2127,39 +2130,39 @@ export class MessageBoxClient {
2127
2130
  // PRIVATE HELPER METHODS
2128
2131
  // ===========================
2129
2132
 
2130
- private static getStatusFromFee(fee: number): 'always_allow' | 'blocked' | 'payment_required' {
2133
+ private static getStatusFromFee (fee: number): 'always_allow' | 'blocked' | 'payment_required' {
2131
2134
  if (fee === -1) return 'blocked'
2132
2135
  if (fee === 0) return 'always_allow'
2133
2136
  return 'payment_required'
2134
2137
  }
2135
2138
 
2136
- /**
2137
- * @method createMessagePayment
2138
- * @private
2139
- * @param {string} recipient - Recipient's identity key.
2140
- * @param {MessageBoxQuote} quote - Quote object containing recipient and delivery fees.
2141
- * @param {string} [description='MessageBox delivery payment'] - Description for the payment action.
2142
- * @param {string} [originator] - Optional originator to use for wallet operations.
2143
- * @returns {Promise<Payment>} - Payment data including the transaction and remittance outputs.
2144
- *
2145
- * @description
2146
- * Constructs and signs a payment transaction covering both delivery and recipient fees for
2147
- * message delivery, based on a previously obtained quote.
2148
- *
2149
- * The transaction includes:
2150
- * - An optional delivery fee output for the MessageBox server.
2151
- * - An optional recipient fee output for the message recipient.
2152
- *
2153
- * Payment remittance metadata (derivation prefix/suffix, sender identity) is embedded to allow
2154
- * the payee to derive their private key and spend the output.
2155
- *
2156
- * @throws {Error} If no payment is required, key derivation fails, or the action creation fails.
2157
- *
2158
- * @example
2159
- * const payment = await client.createMessagePayment(recipientKey, quote)
2160
- * await client.sendMessage({ recipient, messageBox, body, payment })
2161
- */
2162
- private async createMessagePayment(
2139
+ /**
2140
+ * @method createMessagePayment
2141
+ * @private
2142
+ * @param {string} recipient - Recipient's identity key.
2143
+ * @param {MessageBoxQuote} quote - Quote object containing recipient and delivery fees.
2144
+ * @param {string} [description='MessageBox delivery payment'] - Description for the payment action.
2145
+ * @param {string} [originator] - Optional originator to use for wallet operations.
2146
+ * @returns {Promise<Payment>} - Payment data including the transaction and remittance outputs.
2147
+ *
2148
+ * @description
2149
+ * Constructs and signs a payment transaction covering both delivery and recipient fees for
2150
+ * message delivery, based on a previously obtained quote.
2151
+ *
2152
+ * The transaction includes:
2153
+ * - An optional delivery fee output for the MessageBox server.
2154
+ * - An optional recipient fee output for the message recipient.
2155
+ *
2156
+ * Payment remittance metadata (derivation prefix/suffix, sender identity) is embedded to allow
2157
+ * the payee to derive their private key and spend the output.
2158
+ *
2159
+ * @throws {Error} If no payment is required, key derivation fails, or the action creation fails.
2160
+ *
2161
+ * @example
2162
+ * const payment = await client.createMessagePayment(recipientKey, quote)
2163
+ * await client.sendMessage({ recipient, messageBox, body, payment })
2164
+ */
2165
+ private async createMessagePayment (
2163
2166
  recipient: string,
2164
2167
  quote: MessageBoxQuote,
2165
2168
  description: string = 'MessageBox delivery payment',
@@ -15,7 +15,7 @@ import { PeerMessage } from './types.js'
15
15
  import { WalletClient, P2PKH, PublicKey, createNonce, AtomicBEEF, AuthFetch, Base64String } from '@bsv/sdk'
16
16
  import * as Logger from './Utils/logger.js'
17
17
 
18
- function safeParse<T>(input: any): T {
18
+ function safeParse<T> (input: any): T {
19
19
  try {
20
20
  return typeof input === 'string' ? JSON.parse(input) : input
21
21
  } catch (e) {
@@ -74,7 +74,7 @@ export class PeerPayClient extends MessageBoxClient {
74
74
  private readonly peerPayWalletClient: WalletClient
75
75
  private _authFetchInstance?: AuthFetch
76
76
 
77
- constructor(config: PeerPayClientConfig) {
77
+ constructor (config: PeerPayClientConfig) {
78
78
  const { messageBoxHost = 'https://messagebox.babbage.systems', walletClient, enableLogging = false } = config
79
79
 
80
80
  // 🔹 Pass enableLogging to MessageBoxClient
@@ -83,7 +83,7 @@ export class PeerPayClient extends MessageBoxClient {
83
83
  this.peerPayWalletClient = walletClient
84
84
  }
85
85
 
86
- private get authFetchInstance(): AuthFetch {
86
+ private get authFetchInstance (): AuthFetch {
87
87
  if (this._authFetchInstance === null || this._authFetchInstance === undefined) {
88
88
  this._authFetchInstance = new AuthFetch(this.peerPayWalletClient)
89
89
  }
@@ -102,7 +102,7 @@ export class PeerPayClient extends MessageBoxClient {
102
102
  * @returns {Promise<PaymentToken>} A valid payment token containing transaction details.
103
103
  * @throws {Error} If the recipient's public key cannot be derived.
104
104
  */
105
- async createPaymentToken(payment: PaymentParams): Promise<PaymentToken> {
105
+ async createPaymentToken (payment: PaymentParams): Promise<PaymentToken> {
106
106
  if (payment.amount <= 0) {
107
107
  throw new Error('Invalid payment details: recipient and valid amount are required')
108
108
  };
@@ -175,10 +175,11 @@ export class PeerPayClient extends MessageBoxClient {
175
175
  * @param {PaymentParams} payment - The payment details.
176
176
  * @param {string} payment.recipient - The recipient's identity key.
177
177
  * @param {number} payment.amount - The amount in satoshis to send.
178
+ * @param {string} [hostOverride] - Optional host override for the message box server.
178
179
  * @returns {Promise<any>} Resolves with the payment result.
179
180
  * @throws {Error} If the recipient is missing or the amount is invalid.
180
181
  */
181
- async sendPayment(payment: PaymentParams): Promise<any> {
182
+ async sendPayment (payment: PaymentParams, hostOverride?: string): Promise<any> {
182
183
  if (payment.recipient == null || payment.recipient.trim() === '' || payment.amount <= 0) {
183
184
  throw new Error('Invalid payment details: recipient and valid amount are required')
184
185
  }
@@ -190,7 +191,7 @@ export class PeerPayClient extends MessageBoxClient {
190
191
  recipient: payment.recipient,
191
192
  messageBox: STANDARD_PAYMENT_MESSAGEBOX,
192
193
  body: JSON.stringify(paymentToken)
193
- })
194
+ }, hostOverride)
194
195
  }
195
196
 
196
197
  /**
@@ -206,7 +207,7 @@ export class PeerPayClient extends MessageBoxClient {
206
207
  * @returns {Promise<void>} Resolves when the payment has been sent.
207
208
  * @throws {Error} If payment token generation fails.
208
209
  */
209
- async sendLivePayment(payment: PaymentParams): Promise<void> {
210
+ async sendLivePayment (payment: PaymentParams): Promise<void> {
210
211
  const paymentToken = await this.createPaymentToken(payment)
211
212
 
212
213
  try {
@@ -239,11 +240,16 @@ export class PeerPayClient extends MessageBoxClient {
239
240
  * @param {Function} obj.onPayment - Callback function triggered when a payment is received.
240
241
  * @returns {Promise<void>} Resolves when the listener is successfully set up.
241
242
  */
242
- async listenForLivePayments({
243
- onPayment
244
- }: { onPayment: (payment: IncomingPayment) => void }): Promise<void> {
243
+ async listenForLivePayments ({
244
+ onPayment,
245
+ overrideHost
246
+ }: {
247
+ onPayment: (payment: IncomingPayment) => void
248
+ overrideHost?: string
249
+ }): Promise<void> {
245
250
  await this.listenForLiveMessages({
246
251
  messageBox: STANDARD_PAYMENT_MESSAGEBOX,
252
+ overrideHost,
247
253
 
248
254
  // Convert PeerMessage → IncomingPayment before calling onPayment
249
255
  onMessage: (message: PeerMessage) => {
@@ -270,7 +276,7 @@ export class PeerPayClient extends MessageBoxClient {
270
276
  * @returns {Promise<any>} Resolves with the payment result if successful.
271
277
  * @throws {Error} If payment processing fails.
272
278
  */
273
- async acceptPayment(payment: IncomingPayment): Promise<any> {
279
+ async acceptPayment (payment: IncomingPayment): Promise<any> {
274
280
  try {
275
281
  Logger.log(`[PP CLIENT] Processing payment: ${JSON.stringify(payment, null, 2)}`)
276
282
 
@@ -310,7 +316,7 @@ export class PeerPayClient extends MessageBoxClient {
310
316
  * @param {IncomingPayment} payment - The payment object containing transaction details.
311
317
  * @returns {Promise<void>} Resolves when the payment is either acknowledged or refunded.
312
318
  */
313
- async rejectPayment(payment: IncomingPayment): Promise<void> {
319
+ async rejectPayment (payment: IncomingPayment): Promise<void> {
314
320
  Logger.log(`[PP CLIENT] Rejecting payment: ${JSON.stringify(payment, null, 2)}`)
315
321
 
316
322
  if (payment.token.amount - 1000 < 1000) {
@@ -368,10 +374,11 @@ export class PeerPayClient extends MessageBoxClient {
368
374
  * This function queries the message box for new messages and transforms
369
375
  * them into `IncomingPayment` objects by extracting relevant fields.
370
376
  *
377
+ * @param {string} [overrideHost] - Optional host override to list payments from
371
378
  * @returns {Promise<IncomingPayment[]>} Resolves with an array of pending payments.
372
379
  */
373
- async listIncomingPayments(): Promise<IncomingPayment[]> {
374
- const messages = await this.listMessages({ messageBox: STANDARD_PAYMENT_MESSAGEBOX })
380
+ async listIncomingPayments (overrideHost?: string): Promise<IncomingPayment[]> {
381
+ const messages = await this.listMessages({ messageBox: STANDARD_PAYMENT_MESSAGEBOX, host: overrideHost })
375
382
 
376
383
  return messages.map((msg: any) => {
377
384
  const parsedToken = safeParse<PaymentToken>(msg.body)
@@ -320,7 +320,6 @@ describe('MessageBoxClient', () => {
320
320
 
321
321
  const result = await messageBoxClient.listMessages({ messageBox: 'test_inbox' })
322
322
 
323
-
324
323
  expect(result).toEqual(JSON.parse(VALID_LIST_AND_READ_RESULT.body).messages)
325
324
  })
326
325
 
@@ -438,28 +437,6 @@ describe('MessageBoxClient', () => {
438
437
  .rejects.toThrow('Failed to acknowledge messages')
439
438
  })
440
439
 
441
- it('Throws an error when WebSocket is not initialized before listening for messages', async () => {
442
- const messageBoxClient = new MessageBoxClient({
443
- walletClient: mockWalletClient,
444
- host: 'https://messagebox.babbage.systems',
445
- enableLogging: true
446
- })
447
- await messageBoxClient.init()
448
-
449
- // Stub out the identity key to pass that check
450
- ; (messageBoxClient as any).myIdentityKey = '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4'
451
-
452
- // Stub out joinRoom to throw like the real one might
453
- jest.spyOn(messageBoxClient, 'joinRoom').mockRejectedValue(new Error('WebSocket connection not initialized'))
454
-
455
- await expect(
456
- messageBoxClient.listenForLiveMessages({
457
- onMessage: jest.fn(),
458
- messageBox: 'test_inbox'
459
- })
460
- ).rejects.toThrow('WebSocket connection not initialized')
461
- })
462
-
463
440
  it('Emits joinRoom event and listens for incoming messages', async () => {
464
441
  const messageBoxClient = new MessageBoxClient({
465
442
  walletClient: mockWalletClient,
@@ -119,7 +119,7 @@ describe('PeerPayClient Unit Tests', () => {
119
119
  recipient: 'recipientKey',
120
120
  messageBox: 'payment_inbox',
121
121
  body: expect.any(String)
122
- })
122
+ }, undefined)
123
123
  }, 10000)
124
124
  })
125
125
 
@@ -147,7 +147,7 @@ describe('PeerPayClient Unit Tests', () => {
147
147
  expect(peerPayClient.sendLiveMessage).toHaveBeenCalledWith({
148
148
  recipient: 'recipientKey',
149
149
  messageBox: 'payment_inbox',
150
- body: "{\"customInstructions\":{\"derivationPrefix\":\"prefix\",\"derivationSuffix\":\"suffix\"},\"transaction\":[1,2,3,4,5],\"amount\":2}"
150
+ body: '{"customInstructions":{"derivationPrefix":"prefix","derivationSuffix":"suffix"},"transaction":[1,2,3,4,5],"amount":2}'
151
151
  })
152
152
  })
153
153
  })