@bsv/message-box-client 1.1.11 → 1.2.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/message-box-client",
3
- "version": "1.1.11",
3
+ "version": "1.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -31,7 +31,6 @@ import {
31
31
  Transaction,
32
32
  PushDrop,
33
33
  PubKeyHex,
34
- createNonce,
35
34
  P2PKH,
36
35
  PublicKey,
37
36
  CreateActionOutput,
@@ -122,7 +121,8 @@ export class MessageBoxClient {
122
121
  host,
123
122
  walletClient,
124
123
  enableLogging = false,
125
- networkPreset = 'mainnet'
124
+ networkPreset = 'mainnet',
125
+ originator = undefined
126
126
  } = options
127
127
 
128
128
  const defaultHost =
@@ -132,7 +132,7 @@ export class MessageBoxClient {
132
132
 
133
133
  this.host = host?.trim() ?? defaultHost
134
134
 
135
- this.walletClient = walletClient ?? new WalletClient()
135
+ this.walletClient = walletClient ?? new WalletClient('auto', originator)
136
136
  this.authFetch = new AuthFetch(this.walletClient)
137
137
  this.networkPreset = networkPreset
138
138
 
@@ -149,6 +149,7 @@ export class MessageBoxClient {
149
149
  * @method init
150
150
  * @async
151
151
  * @param {string} [targetHost] - Optional host to set or override the default host.
152
+ * @param {string} [originator] - Optional originator to use with walletClient.
152
153
  * @returns {Promise<void>}
153
154
  *
154
155
  * @description
@@ -167,7 +168,7 @@ export class MessageBoxClient {
167
168
  * await client.init()
168
169
  * await client.sendMessage({ recipient, messageBox: 'inbox', body: 'Hello' })
169
170
  */
170
- async init(targetHost: string = this.host): Promise<void> {
171
+ async init(targetHost: string = this.host, originator?: string): Promise<void> {
171
172
  const normalizedHost = targetHost?.trim()
172
173
  if (normalizedHost === '') {
173
174
  throw new Error('Cannot anoint host: No valid host provided')
@@ -182,13 +183,13 @@ export class MessageBoxClient {
182
183
  if (this.initialized) return
183
184
 
184
185
  // 1. Get our identity key
185
- const identityKey = await this.getIdentityKey()
186
+ const identityKey = await this.getIdentityKey(originator)
186
187
  // 2. Check for any matching advertisements for the given host
187
- const [firstAdvertisement] = await this.queryAdvertisements(identityKey, normalizedHost)
188
+ const [firstAdvertisement] = await this.queryAdvertisements(identityKey, normalizedHost, originator)
188
189
  // 3. If none our found, anoint this host
189
190
  if (firstAdvertisement == null || firstAdvertisement?.host?.trim() === '' || firstAdvertisement?.host !== normalizedHost) {
190
191
  Logger.log('[MB CLIENT] Anointing host:', normalizedHost)
191
- const { txid } = await this.anointHost(normalizedHost)
192
+ const { txid } = await this.anointHost(normalizedHost, originator)
192
193
  if (txid == null || txid.trim() === '') {
193
194
  throw new Error('Failed to anoint host: No transaction ID returned')
194
195
  }
@@ -226,19 +227,20 @@ export class MessageBoxClient {
226
227
 
227
228
  /**
228
229
  * @method getIdentityKey
230
+ * @param {string} [originator] - Optional originator to use for identity key lookup
229
231
  * @returns {Promise<string>} The identity public key of the user
230
232
  * @description
231
233
  * Returns the client's identity key, used for signing, encryption, and addressing.
232
234
  * If not already loaded, it will fetch and cache it.
233
235
  */
234
- public async getIdentityKey(): Promise<string> {
236
+ public async getIdentityKey(originator?: string): Promise<string> {
235
237
  if (this.myIdentityKey != null && this.myIdentityKey.trim() !== '') {
236
238
  return this.myIdentityKey
237
239
  }
238
240
 
239
241
  Logger.log('[MB CLIENT] Fetching identity key...')
240
242
  try {
241
- const keyResult = await this.walletClient.getPublicKey({ identityKey: true })
243
+ const keyResult = await this.walletClient.getPublicKey({ identityKey: true }, originator)
242
244
  this.myIdentityKey = keyResult.publicKey
243
245
  Logger.log(`[MB CLIENT] Identity key fetched: ${this.myIdentityKey}`)
244
246
  return this.myIdentityKey
@@ -265,6 +267,7 @@ export class MessageBoxClient {
265
267
 
266
268
  /**
267
269
  * @method initializeConnection
270
+ * @param {string} [originator] - Optional originator to use for authentication.
268
271
  * @async
269
272
  * @returns {Promise<void>}
270
273
  * @description
@@ -286,20 +289,12 @@ export class MessageBoxClient {
286
289
  * await mb.initializeConnection()
287
290
  * // WebSocket is now ready for use
288
291
  */
289
- async initializeConnection(): Promise<void> {
292
+ async initializeConnection(originator?: string): Promise<void> {
290
293
  await this.assertInitialized()
291
294
  Logger.log('[MB CLIENT] initializeConnection() STARTED')
292
295
 
293
296
  if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
294
- Logger.log('[MB CLIENT] Fetching identity key...')
295
- try {
296
- const keyResult = await this.walletClient.getPublicKey({ identityKey: true })
297
- this.myIdentityKey = keyResult.publicKey
298
- Logger.log(`[MB CLIENT] Identity key fetched successfully: ${this.myIdentityKey}`)
299
- } catch (error) {
300
- Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error)
301
- throw new Error('Identity key retrieval failed')
302
- }
297
+ await this.getIdentityKey(originator)
303
298
  }
304
299
 
305
300
  if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
@@ -373,6 +368,7 @@ export class MessageBoxClient {
373
368
  * @method resolveHostForRecipient
374
369
  * @async
375
370
  * @param {string} identityKey - The public identity key of the intended recipient.
371
+ * @param {string} [originator] - The originator to use for the WalletClient.
376
372
  * @returns {Promise<string>} - A fully qualified host URL for the recipient's MessageBox server.
377
373
  *
378
374
  * @description
@@ -387,8 +383,8 @@ export class MessageBoxClient {
387
383
  * @example
388
384
  * const host = await resolveHostForRecipient('028d...') // → returns either overlay host or this.host
389
385
  */
390
- async resolveHostForRecipient(identityKey: string): Promise<string> {
391
- const advertisementTokens = await this.queryAdvertisements(identityKey)
386
+ async resolveHostForRecipient(identityKey: string, originator?: string): Promise<string> {
387
+ const advertisementTokens = await this.queryAdvertisements(identityKey, undefined, originator)
392
388
  if (advertisementTokens.length === 0) {
393
389
  Logger.warn(`[MB CLIENT] No advertisements for ${identityKey}, using default host ${this.host}`)
394
390
  return this.host
@@ -407,11 +403,12 @@ export class MessageBoxClient {
407
403
  */
408
404
  async queryAdvertisements(
409
405
  identityKey?: string,
410
- host?: string
406
+ host?: string,
407
+ originator?: string
411
408
  ): Promise<AdvertisementToken[]> {
412
409
  const hosts: AdvertisementToken[] = []
413
410
  try {
414
- const query: Record<string, string> = { identityKey: identityKey ?? await this.getIdentityKey() }
411
+ const query: Record<string, string> = { identityKey: identityKey ?? await this.getIdentityKey(originator) }
415
412
  if (host != null && host.trim() !== '') query.host = host
416
413
 
417
414
  const result = await this.lookupResolver.query({
@@ -530,10 +527,12 @@ export class MessageBoxClient {
530
527
  */
531
528
  async listenForLiveMessages({
532
529
  onMessage,
533
- messageBox
530
+ messageBox,
531
+ originator
534
532
  }: {
535
533
  onMessage: (message: PeerMessage) => void
536
534
  messageBox: string
535
+ originator?: string
537
536
  }): Promise<void> {
538
537
  await this.assertInitialized()
539
538
  Logger.log(`[MB CLIENT] Setting up listener for WebSocket room: ${messageBox}`)
@@ -576,7 +575,7 @@ export class MessageBoxClient {
576
575
  keyID: '1',
577
576
  counterparty: message.sender,
578
577
  ciphertext: Utils.toArray((parsedBody as any).encryptedMessage, 'base64')
579
- })
578
+ }, originator)
580
579
 
581
580
  message.body = Utils.toUTF8(decrypted.plaintext)
582
581
  } else {
@@ -630,7 +629,8 @@ export class MessageBoxClient {
630
629
  body,
631
630
  messageId,
632
631
  skipEncryption,
633
- checkPermissions
632
+ checkPermissions,
633
+ originator
634
634
  }: SendMessageParams): Promise<SendMessageResponse> {
635
635
  await this.assertInitialized()
636
636
  if (recipient == null || recipient.trim() === '') {
@@ -660,7 +660,7 @@ export class MessageBoxClient {
660
660
  protocolID: [1, 'messagebox'],
661
661
  keyID: '1',
662
662
  counterparty: recipient
663
- })
663
+ }, originator)
664
664
  finalMessageId = messageId ?? Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('')
665
665
  } catch (error) {
666
666
  Logger.error('[MB CLIENT ERROR] Failed to generate HMAC:', error)
@@ -679,7 +679,7 @@ export class MessageBoxClient {
679
679
  keyID: '1',
680
680
  counterparty: recipient,
681
681
  plaintext: Utils.toArray(typeof body === 'string' ? body : JSON.stringify(body), 'utf8')
682
- })
682
+ }, originator)
683
683
 
684
684
  outgoingBody = JSON.stringify({
685
685
  encryptedMessage: Utils.toBase64(encryptedMessage.ciphertext)
@@ -853,7 +853,8 @@ export class MessageBoxClient {
853
853
  */
854
854
  async sendMessage(
855
855
  message: SendMessageParams,
856
- overrideHost?: string
856
+ overrideHost?: string,
857
+ originator?: string
857
858
  ): Promise<SendMessageResponse> {
858
859
  await this.assertInitialized()
859
860
  if (message.recipient == null || message.recipient.trim() === '') {
@@ -910,7 +911,7 @@ export class MessageBoxClient {
910
911
  protocolID: [1, 'messagebox'],
911
912
  keyID: '1',
912
913
  counterparty: message.recipient
913
- })
914
+ }, originator)
914
915
  messageId = message.messageId ?? Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('')
915
916
  } catch (error) {
916
917
  Logger.error('[MB CLIENT ERROR] Failed to generate HMAC:', error)
@@ -926,7 +927,7 @@ export class MessageBoxClient {
926
927
  keyID: '1',
927
928
  counterparty: message.recipient,
928
929
  plaintext: Utils.toArray(typeof message.body === 'string' ? message.body : JSON.stringify(message.body), 'utf8')
929
- })
930
+ }, originator)
930
931
 
931
932
  finalBody = JSON.stringify({ encryptedMessage: Utils.toBase64(encryptedMessage.ciphertext) })
932
933
  }
@@ -948,7 +949,7 @@ export class MessageBoxClient {
948
949
 
949
950
  if (this.myIdentityKey == null || this.myIdentityKey === '') {
950
951
  try {
951
- const keyResult = await this.walletClient.getPublicKey({ identityKey: true })
952
+ const keyResult = await this.walletClient.getPublicKey({ identityKey: true }, originator)
952
953
  this.myIdentityKey = keyResult.publicKey
953
954
  Logger.log(`[MB CLIENT] Fetched identity key before sending request: ${this.myIdentityKey}`)
954
955
  } catch (error) {
@@ -1014,14 +1015,14 @@ export class MessageBoxClient {
1014
1015
  * @example
1015
1016
  * const { txid } = await client.anointHost('https://my-messagebox.io')
1016
1017
  */
1017
- async anointHost(host: string): Promise<{ txid: string }> {
1018
+ async anointHost(host: string, originator?: string): Promise<{ txid: string }> {
1018
1019
  Logger.log('[MB CLIENT] Starting anointHost...')
1019
1020
  try {
1020
1021
  if (!host.startsWith('http')) {
1021
1022
  throw new Error('Invalid host URL')
1022
1023
  }
1023
1024
 
1024
- const identityKey = await this.getIdentityKey()
1025
+ const identityKey = await this.getIdentityKey(originator)
1025
1026
 
1026
1027
  Logger.log('[MB CLIENT] Fields - Identity:', identityKey, 'Host:', host)
1027
1028
 
@@ -1030,7 +1031,7 @@ export class MessageBoxClient {
1030
1031
  Utils.toArray(host, 'utf8')
1031
1032
  ]
1032
1033
 
1033
- const pushdrop = new PushDrop(this.walletClient)
1034
+ const pushdrop = new PushDrop(this.walletClient, originator)
1034
1035
  Logger.log('Fields:', fields.map(a => Utils.toHex(a)))
1035
1036
  Logger.log('ProtocolID:', [1, 'messagebox advertisement'])
1036
1037
  Logger.log('KeyID:', '1')
@@ -1056,7 +1057,7 @@ export class MessageBoxClient {
1056
1057
  outputDescription: 'Overlay advertisement output'
1057
1058
  }],
1058
1059
  options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
1059
- })
1060
+ }, originator)
1060
1061
 
1061
1062
  Logger.log('[MB CLIENT] Transaction created:', txid)
1062
1063
 
@@ -1086,6 +1087,7 @@ export class MessageBoxClient {
1086
1087
  * @method revokeHostAdvertisement
1087
1088
  * @async
1088
1089
  * @param {AdvertisementToken} advertisementToken - The advertisement token containing the messagebox host to revoke.
1090
+ * @param {string} [originator] - Optional originator to use with walletClient.
1089
1091
  * @returns {Promise<{ txid: string }>} - The transaction ID of the revocation broadcast to the overlay network.
1090
1092
  *
1091
1093
  * @description
@@ -1095,7 +1097,7 @@ export class MessageBoxClient {
1095
1097
  * @example
1096
1098
  * const { txid } = await client.revokeHost('https://my-messagebox.io')
1097
1099
  */
1098
- async revokeHostAdvertisement(advertisementToken: AdvertisementToken): Promise<{ txid: string }> {
1100
+ async revokeHostAdvertisement(advertisementToken: AdvertisementToken, originator?: string): Promise<{ txid: string }> {
1099
1101
  Logger.log('[MB CLIENT] Starting revokeHost...')
1100
1102
  const outpoint = `${advertisementToken.txid}.${advertisementToken.outputIndex}`
1101
1103
  try {
@@ -1109,7 +1111,7 @@ export class MessageBoxClient {
1109
1111
  inputDescription: 'Revoking host advertisement token'
1110
1112
  }
1111
1113
  ]
1112
- })
1114
+ }, originator)
1113
1115
 
1114
1116
  if (signableTransaction === undefined) {
1115
1117
  throw new Error('Failed to create signable transaction.')
@@ -1118,7 +1120,7 @@ export class MessageBoxClient {
1118
1120
  const partialTx = Transaction.fromBEEF(signableTransaction.tx)
1119
1121
 
1120
1122
  // Prepare the unlocker
1121
- const pushdrop = new PushDrop(this.walletClient)
1123
+ const pushdrop = new PushDrop(this.walletClient, originator)
1122
1124
  const unlocker = await pushdrop.unlock(
1123
1125
  [1, 'messagebox advertisement'],
1124
1126
  '1',
@@ -1143,7 +1145,7 @@ export class MessageBoxClient {
1143
1145
  options: {
1144
1146
  acceptDelayedBroadcast: false
1145
1147
  }
1146
- })
1148
+ }, originator)
1147
1149
 
1148
1150
  if (signedTx === undefined) {
1149
1151
  throw new Error('Failed to finalize the transaction signature.')
@@ -1198,7 +1200,7 @@ export class MessageBoxClient {
1198
1200
  * messages.forEach(msg => console.log(msg.sender, msg.body))
1199
1201
  * // Payments included with messages are automatically received
1200
1202
  */
1201
- async listMessages({ messageBox, host }: ListMessagesParams): Promise<PeerMessage[]> {
1203
+ async listMessages({ messageBox, host, originator }: ListMessagesParams): Promise<PeerMessage[]> {
1202
1204
  await this.assertInitialized()
1203
1205
  if (messageBox.trim() === '') {
1204
1206
  throw new Error('MessageBox cannot be empty')
@@ -1206,7 +1208,7 @@ export class MessageBoxClient {
1206
1208
 
1207
1209
  let hosts: string[] = host != null ? [host] : []
1208
1210
  if (hosts.length === 0) {
1209
- const advertisedHosts = await this.queryAdvertisements(await this.getIdentityKey())
1211
+ const advertisedHosts = await this.queryAdvertisements(await this.getIdentityKey(originator), originator)
1210
1212
  hosts = Array.from(new Set([this.host, ...advertisedHosts.map(h => h.host)]))
1211
1213
  }
1212
1214
 
@@ -1282,7 +1284,11 @@ export class MessageBoxClient {
1282
1284
  typeof parsedBody === 'object' &&
1283
1285
  'message' in parsedBody
1284
1286
  ) {
1285
- messageContent = (parsedBody as any).message?.body
1287
+ // Handle wrapped message format (with payment data)
1288
+ const wrappedMessage = (parsedBody as any).message
1289
+ messageContent = typeof wrappedMessage === 'string'
1290
+ ? tryParse(wrappedMessage)
1291
+ : wrappedMessage
1286
1292
  paymentData = (parsedBody as any).payment
1287
1293
  }
1288
1294
 
@@ -1308,7 +1314,7 @@ export class MessageBoxClient {
1308
1314
  tx: paymentData.tx,
1309
1315
  outputs: recipientOutputs,
1310
1316
  description: paymentData.description ?? 'MessageBox recipient payment'
1311
- })
1317
+ }, originator)
1312
1318
 
1313
1319
  if (internalizeResult.accepted) {
1314
1320
  Logger.log(
@@ -1348,18 +1354,16 @@ export class MessageBoxClient {
1348
1354
  keyID: '1',
1349
1355
  counterparty: message.sender,
1350
1356
  ciphertext: Utils.toArray(
1351
- (messageContent as any).encryptedMessage,
1357
+ messageContent.encryptedMessage,
1352
1358
  'base64'
1353
1359
  )
1354
- })
1360
+ }, originator)
1355
1361
 
1356
1362
  const decryptedText = Utils.toUTF8(decrypted.plaintext)
1357
1363
  message.body = tryParse(decryptedText)
1358
1364
  } else {
1359
- // Handle both old format (direct content) and new format (message.body)
1360
- message.body = messageContent != null
1361
- ? (typeof messageContent === 'string' ? messageContent : messageContent)
1362
- : (parsedBody as string | Record<string, any>)
1365
+ // For non-encrypted messages, use the processed content
1366
+ message.body = messageContent ?? parsedBody
1363
1367
  }
1364
1368
  } catch (err) {
1365
1369
  Logger.error(
@@ -1400,7 +1404,7 @@ export class MessageBoxClient {
1400
1404
  * @example
1401
1405
  * await client.acknowledgeMessage({ messageIds: ['msg123', 'msg456'] })
1402
1406
  */
1403
- async acknowledgeMessage({ messageIds, host }: AcknowledgeMessageParams): Promise<string> {
1407
+ async acknowledgeMessage({ messageIds, host, originator }: AcknowledgeMessageParams): Promise<string> {
1404
1408
  await this.assertInitialized()
1405
1409
  if (!Array.isArray(messageIds) || messageIds.length === 0) {
1406
1410
  throw new Error('Message IDs array cannot be empty')
@@ -1411,8 +1415,8 @@ export class MessageBoxClient {
1411
1415
  let hosts: string[] = host != null ? [host] : []
1412
1416
  if (hosts.length === 0) {
1413
1417
  // 1. Determine all hosts (advertised + default)
1414
- const identityKey = await this.getIdentityKey()
1415
- const advertisedHosts = await this.queryAdvertisements(identityKey)
1418
+ const identityKey = await this.getIdentityKey(originator)
1419
+ const advertisedHosts = await this.queryAdvertisements(identityKey, undefined, originator)
1416
1420
  hosts = Array.from(new Set([this.host, ...advertisedHosts.map(h => h.host)]))
1417
1421
  }
1418
1422
 
@@ -1934,7 +1938,8 @@ export class MessageBoxClient {
1934
1938
  private async createMessagePayment(
1935
1939
  recipient: string,
1936
1940
  quote: MessageBoxQuote,
1937
- description: string = 'MessageBox delivery payment'
1941
+ description: string = 'MessageBox delivery payment',
1942
+ originator?: string
1938
1943
  ): Promise<Payment> {
1939
1944
  if (quote.recipientFee <= 0 && quote.deliveryFee <= 0) {
1940
1945
  throw new Error('No payment required')
@@ -1959,7 +1964,7 @@ export class MessageBoxClient {
1959
1964
  protocolID: [2, '3241645161d8'],
1960
1965
  keyID: `${derivationPrefix} ${derivationSuffix}`,
1961
1966
  counterparty: quote.deliveryAgentIdentityKey
1962
- })
1967
+ }, originator)
1963
1968
 
1964
1969
  // Create locking script using host's public key
1965
1970
  const lockingScript = new P2PKH().lock(PublicKey.fromString(derivedKeyResult).toAddress()).toHex()
@@ -2024,7 +2029,7 @@ export class MessageBoxClient {
2024
2029
  paymentRemittance: {
2025
2030
  derivationPrefix,
2026
2031
  derivationSuffix,
2027
- senderIdentityKey
2032
+ senderIdentityKey: (await anyoneWallet.getPublicKey({ identityKey: true })).publicKey
2028
2033
  }
2029
2034
  })
2030
2035
  }
@@ -2033,7 +2038,7 @@ export class MessageBoxClient {
2033
2038
  description,
2034
2039
  outputs: createActionOutputs,
2035
2040
  options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
2036
- })
2041
+ }, originator)
2037
2042
 
2038
2043
  if (tx == null) {
2039
2044
  throw new Error('Failed to create payment transaction')
package/src/types.ts CHANGED
@@ -27,6 +27,11 @@ export interface MessageBoxClientOptions {
27
27
  * @default 'local'
28
28
  */
29
29
  networkPreset?: 'local' | 'mainnet' | 'testnet'
30
+
31
+ /**
32
+ * Originator of the message box client.
33
+ */
34
+ originator?: string
30
35
  }
31
36
 
32
37
  /**
@@ -64,6 +69,7 @@ export interface SendMessageParams {
64
69
  skipEncryption?: boolean
65
70
  /** Optional: Enable permission and fee checking (default: false for backwards compatibility) */
66
71
  checkPermissions?: boolean
72
+ originator?: string
67
73
  }
68
74
 
69
75
  /**
@@ -83,10 +89,12 @@ export interface SendMessageResponse {
83
89
  *
84
90
  * @property {string[]} messageIds - An array of message IDs to acknowledge.
85
91
  * @property {string} [host] - Optional host URL where the messages originated.
92
+ * @property {string} [originator] - Optional originator of the message box client.
86
93
  */
87
94
  export interface AcknowledgeMessageParams {
88
95
  messageIds: string[]
89
96
  host?: string
97
+ originator?: string
90
98
  }
91
99
 
92
100
  /**
@@ -94,10 +102,12 @@ export interface AcknowledgeMessageParams {
94
102
  *
95
103
  * @property messageBox - The identifier of the message box to retrieve messages from.
96
104
  * @property host - (Optional) The host URL to connect to for retrieving messages.
105
+ * @property originator - (Optional) The originator of the message box client.
97
106
  */
98
107
  export interface ListMessagesParams {
99
108
  messageBox: string
100
109
  host?: string
110
+ originator?: string
101
111
  }
102
112
 
103
113
  /**