@bsv/message-box-client 1.4.5 → 2.0.1

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.
@@ -1,9 +1,17 @@
1
1
  {
2
2
  "name": "@bsv/message-box-client",
3
- "version": "1.4.5",
3
+ "version": "2.0.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/bsv-blockchain/message-box-client.git"
10
+ },
11
+ "homepage": "https://github.com/bsv-blockchain/message-box-client#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/bsv-blockchain/message-box-client/issues"
14
+ },
7
15
  "description": "A client for P2P messaging and payments",
8
16
  "type": "commonjs",
9
17
  "files": [
@@ -35,8 +43,8 @@
35
43
  "author": "BSV Blockchain Association",
36
44
  "license": "SEE LICENSE IN LICENSE.txt",
37
45
  "devDependencies": {
38
- "@bsv/auth-express-middleware": "^1.2.3",
39
- "@bsv/payment-express-middleware": "^1.2.3",
46
+ "@bsv/auth-express-middleware": "^2.0.2",
47
+ "@bsv/payment-express-middleware": "^2.0.0",
40
48
  "@eslint/js": "^9.20.0",
41
49
  "@types/jest": "^29.5.14",
42
50
  "@types/node": "^22.13.2",
@@ -63,7 +71,7 @@
63
71
  "webpack-merge": "^6.0.1"
64
72
  },
65
73
  "dependencies": {
66
- "@bsv/authsocket-client": "^1.0.12",
67
- "@bsv/sdk": "^1.8.8"
74
+ "@bsv/authsocket-client": "^2.0.1",
75
+ "@bsv/sdk": "^2.0.3"
68
76
  }
69
77
  }
@@ -928,7 +928,8 @@ class MessageBoxClient {
928
928
  messageBox
929
929
  }, overrideHost);
930
930
  const quotesByRecipient = Array.isArray(quoteResponse === null || quoteResponse === void 0 ? void 0 : quoteResponse.quotesByRecipient)
931
- ? quoteResponse.quotesByRecipient : [];
931
+ ? quoteResponse.quotesByRecipient
932
+ : [];
932
933
  const blocked = ((_a = quoteResponse === null || quoteResponse === void 0 ? void 0 : quoteResponse.blockedRecipients) !== null && _a !== void 0 ? _a : []);
933
934
  const totals = quoteResponse === null || quoteResponse === void 0 ? void 0 : quoteResponse.totals;
934
935
  // 2) Filter allowed recipients
@@ -969,17 +970,16 @@ class MessageBoxClient {
969
970
  this.myIdentityKey = keyResult.publicKey;
970
971
  }
971
972
  // 6) Build per-recipient messageIds (HMAC), same order as allowedRecipients
972
- const messageIds = [];
973
- for (const r of allowedRecipients) {
973
+ const bodyBytes = Array.from(new TextEncoder().encode(JSON.stringify(body)));
974
+ const messageIds = await this.mapWithConcurrency(allowedRecipients, 8, async (r) => {
974
975
  const hmac = await this.walletClient.createHmac({
975
- data: Array.from(new TextEncoder().encode(JSON.stringify(body))),
976
+ data: bodyBytes,
976
977
  protocolID: [1, 'messagebox'],
977
978
  keyID: '1',
978
979
  counterparty: r
979
980
  }, this.originator);
980
- const mid = Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('');
981
- messageIds.push(mid);
982
- }
981
+ return Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('');
982
+ });
983
983
  // 7) Body: for batch route the server expects a single shared body
984
984
  // NOTE: If you need per-recipient encryption, we must change the server payload shape.
985
985
  let finalBody;
@@ -1018,8 +1018,10 @@ class MessageBoxClient {
1018
1018
  // server returns { results: [{ recipient, messageId }] }
1019
1019
  const sent = Array.isArray(parsed.results) ? parsed.results : [];
1020
1020
  const failed = []; // handled server-side now
1021
- const status = sent.length === allowedRecipients.length ? 'success'
1022
- : sent.length > 0 ? 'partial'
1021
+ const status = sent.length === allowedRecipients.length
1022
+ ? 'success'
1023
+ : sent.length > 0
1024
+ ? 'partial'
1023
1025
  : 'error';
1024
1026
  const description = status === 'success'
1025
1027
  ? `Sent to ${sent.length} recipients.`
@@ -1213,7 +1215,6 @@ class MessageBoxClient {
1213
1215
  * // Payments included with messages are automatically received
1214
1216
  */
1215
1217
  async listMessages({ messageBox, host, acceptPayments }) {
1216
- var _a;
1217
1218
  if (typeof acceptPayments !== 'boolean') {
1218
1219
  acceptPayments = true;
1219
1220
  }
@@ -1275,75 +1276,79 @@ class MessageBoxClient {
1275
1276
  if (dedupMap.size === 0)
1276
1277
  return [];
1277
1278
  const messages = Array.from(dedupMap.values());
1278
- for (const message of messages) {
1279
- try {
1280
- const parsedBody = typeof message.body === 'string' ? this.tryParse(message.body) : message.body;
1281
- let messageContent = parsedBody;
1282
- let paymentData;
1283
- if (parsedBody != null &&
1284
- typeof parsedBody === 'object' &&
1285
- 'message' in parsedBody) {
1286
- // Handle wrapped message format (with payment data)
1287
- const wrappedMessage = parsedBody.message;
1288
- messageContent = typeof wrappedMessage === 'string'
1289
- ? this.tryParse(wrappedMessage)
1290
- : wrappedMessage;
1291
- paymentData = parsedBody.payment;
1292
- }
1293
- // Process payment if present - server now only stores recipient payments
1294
- if (acceptPayments && (paymentData === null || paymentData === void 0 ? void 0 : paymentData.tx) != null && paymentData.outputs != null) {
1295
- try {
1296
- Logger.log(`[MB CLIENT] Processing recipient payment in message from ${String(message.sender)}…`);
1297
- // All outputs in the stored payment data are for the recipient
1298
- // (delivery fees are already processed by the server)
1299
- const recipientOutputs = paymentData.outputs.filter(output => output.protocol === 'wallet payment');
1300
- if (recipientOutputs.length > 0) {
1301
- Logger.log(`[MB CLIENT] Internalizing ${recipientOutputs.length} recipient payment output(s)…`);
1302
- const internalizeResult = await this.walletClient.internalizeAction({
1303
- tx: paymentData.tx,
1304
- outputs: recipientOutputs,
1305
- description: (_a = paymentData.description) !== null && _a !== void 0 ? _a : 'MessageBox recipient payment'
1306
- }, this.originator);
1307
- if (internalizeResult.accepted) {
1308
- Logger.log('[MB CLIENT] Successfully internalized recipient payment');
1309
- }
1310
- else {
1311
- Logger.warn('[MB CLIENT] Recipient payment internalization was not accepted');
1312
- }
1279
+ const parsed = messages.map(message => {
1280
+ const parsedBody = typeof message.body === 'string' ? this.tryParse(message.body) : message.body;
1281
+ let messageContent = parsedBody;
1282
+ let paymentData;
1283
+ if (parsedBody != null &&
1284
+ typeof parsedBody === 'object' &&
1285
+ 'message' in parsedBody) {
1286
+ const wrappedMessage = parsedBody.message;
1287
+ messageContent = typeof wrappedMessage === 'string'
1288
+ ? this.tryParse(wrappedMessage)
1289
+ : wrappedMessage;
1290
+ paymentData = parsedBody.payment;
1291
+ }
1292
+ return { message, parsedBody, messageContent, paymentData };
1293
+ });
1294
+ if (acceptPayments) {
1295
+ const paymentJobs = parsed
1296
+ .filter(p => { var _a; return ((_a = p.paymentData) === null || _a === void 0 ? void 0 : _a.tx) != null && p.paymentData.outputs != null; });
1297
+ await this.mapWithConcurrency(paymentJobs, 2, async (p) => {
1298
+ var _a;
1299
+ try {
1300
+ Logger.log(`[MB CLIENT] Processing recipient payment in message from ${String(p.message.sender)}…`);
1301
+ const recipientOutputs = p.paymentData.outputs.filter(output => output.protocol === 'wallet payment');
1302
+ if (recipientOutputs.length > 0) {
1303
+ Logger.log(`[MB CLIENT] Internalizing ${recipientOutputs.length} recipient payment output(s)…`);
1304
+ const internalizeResult = await this.walletClient.internalizeAction({
1305
+ tx: p.paymentData.tx,
1306
+ outputs: recipientOutputs,
1307
+ description: (_a = p.paymentData.description) !== null && _a !== void 0 ? _a : 'MessageBox recipient payment'
1308
+ }, this.originator);
1309
+ if (internalizeResult.accepted) {
1310
+ Logger.log('[MB CLIENT] Successfully internalized recipient payment');
1313
1311
  }
1314
1312
  else {
1315
- Logger.log('[MB CLIENT] No wallet payment outputs found in payment data');
1313
+ Logger.warn('[MB CLIENT] Recipient payment internalization was not accepted');
1316
1314
  }
1317
1315
  }
1318
- catch (paymentError) {
1319
- Logger.error('[MB CLIENT ERROR] Failed to internalize recipient payment:', paymentError);
1320
- // Continue processing the message even if payment fails
1316
+ else {
1317
+ Logger.log('[MB CLIENT] No wallet payment outputs found in payment data');
1321
1318
  }
1322
1319
  }
1323
- // Handle message decryption
1324
- if (messageContent != null &&
1325
- typeof messageContent === 'object' &&
1326
- typeof (messageContent).encryptedMessage === 'string') {
1327
- Logger.log(`[MB CLIENT] Decrypting message from ${String(message.sender)}…`);
1320
+ catch (paymentError) {
1321
+ Logger.error('[MB CLIENT ERROR] Failed to internalize recipient payment:', paymentError);
1322
+ }
1323
+ return null;
1324
+ });
1325
+ }
1326
+ await this.mapWithConcurrency(parsed, 4, async (p) => {
1327
+ var _a;
1328
+ try {
1329
+ if (p.messageContent != null &&
1330
+ typeof p.messageContent === 'object' &&
1331
+ typeof (p.messageContent).encryptedMessage === 'string') {
1332
+ Logger.log(`[MB CLIENT] Decrypting message from ${String(p.message.sender)}…`);
1328
1333
  const decrypted = await this.walletClient.decrypt({
1329
1334
  protocolID: [1, 'messagebox'],
1330
1335
  keyID: '1',
1331
- counterparty: message.sender,
1332
- ciphertext: sdk_1.Utils.toArray(messageContent.encryptedMessage, 'base64')
1336
+ counterparty: p.message.sender,
1337
+ ciphertext: sdk_1.Utils.toArray((p.messageContent).encryptedMessage, 'base64')
1333
1338
  }, this.originator);
1334
1339
  const decryptedText = sdk_1.Utils.toUTF8(decrypted.plaintext);
1335
- message.body = this.tryParse(decryptedText);
1340
+ p.message.body = this.tryParse(decryptedText);
1336
1341
  }
1337
1342
  else {
1338
- // For non-encrypted messages, use the processed content
1339
- message.body = messageContent !== null && messageContent !== void 0 ? messageContent : parsedBody;
1343
+ p.message.body = (_a = p.messageContent) !== null && _a !== void 0 ? _a : p.parsedBody;
1340
1344
  }
1341
1345
  }
1342
1346
  catch (err) {
1343
1347
  Logger.error('[MB CLIENT ERROR] Failed to parse or decrypt message in list:', err);
1344
- message.body = '[Error: Failed to decrypt or parse message]';
1348
+ p.message.body = '[Error: Failed to decrypt or parse message]';
1345
1349
  }
1346
- }
1350
+ return null;
1351
+ });
1347
1352
  // Sort newest‑first for a deterministic order
1348
1353
  messages.sort((a, b) => { var _a, _b; return Number((_a = b.timestamp) !== null && _a !== void 0 ? _a : 0) - Number((_b = a.timestamp) !== null && _b !== void 0 ? _b : 0); });
1349
1354
  return messages;
@@ -1398,20 +1403,18 @@ class MessageBoxClient {
1398
1403
  return raw;
1399
1404
  }
1400
1405
  };
1401
- for (const message of messages) {
1406
+ await this.mapWithConcurrency(messages, 4, async (message) => {
1402
1407
  try {
1403
1408
  const parsedBody = typeof message.body === 'string' ? tryParse(message.body) : message.body;
1404
1409
  let messageContent = parsedBody;
1405
1410
  if (parsedBody != null &&
1406
1411
  typeof parsedBody === 'object' &&
1407
1412
  'message' in parsedBody) {
1408
- // Handle wrapped message format (with payment data)
1409
1413
  const wrappedMessage = parsedBody.message;
1410
1414
  messageContent = typeof wrappedMessage === 'string'
1411
1415
  ? tryParse(wrappedMessage)
1412
1416
  : wrappedMessage;
1413
1417
  }
1414
- // Handle message decryption
1415
1418
  if (messageContent != null &&
1416
1419
  typeof messageContent === 'object' &&
1417
1420
  typeof messageContent.encryptedMessage === 'string') {
@@ -1425,7 +1428,6 @@ class MessageBoxClient {
1425
1428
  message.body = tryParse(decryptedText);
1426
1429
  }
1427
1430
  else {
1428
- // For non-encrypted messages, use the processed content
1429
1431
  message.body = messageContent !== null && messageContent !== void 0 ? messageContent : parsedBody;
1430
1432
  }
1431
1433
  }
@@ -1433,7 +1435,8 @@ class MessageBoxClient {
1433
1435
  Logger.error('[MB CLIENT ERROR] Failed to parse or decrypt message in list:', err);
1434
1436
  message.body = '[Error: Failed to decrypt or parse message]';
1435
1437
  }
1436
- }
1438
+ return null;
1439
+ });
1437
1440
  return messages;
1438
1441
  }
1439
1442
  /**
@@ -1461,6 +1464,27 @@ class MessageBoxClient {
1461
1464
  return raw;
1462
1465
  }
1463
1466
  }
1467
+ async mapWithConcurrency(items, limit, fn) {
1468
+ if (items.length === 0)
1469
+ return [];
1470
+ if (!Number.isFinite(limit) || limit >= items.length) {
1471
+ return await Promise.all(items.map(fn));
1472
+ }
1473
+ const workerCount = Math.max(1, Math.min(limit, items.length));
1474
+ const results = new Array(items.length);
1475
+ let nextIndex = 0;
1476
+ const workers = Array.from({ length: workerCount }, async () => {
1477
+ while (true) {
1478
+ const currentIndex = nextIndex;
1479
+ nextIndex++;
1480
+ if (currentIndex >= items.length)
1481
+ return;
1482
+ results[currentIndex] = await fn(items[currentIndex], currentIndex);
1483
+ }
1484
+ });
1485
+ await Promise.all(workers);
1486
+ return results;
1487
+ }
1464
1488
  /**
1465
1489
  * @method acknowledgeNotification
1466
1490
  * @async
@@ -1706,9 +1730,9 @@ class MessageBoxClient {
1706
1730
  messageBox: params.messageBox
1707
1731
  });
1708
1732
  Logger.log('[MB CLIENT] Getting messageBox quote (single)...');
1709
- console.log("HELP IM QUOTING", `${finalHost}/permissions/quote?${queryParams.toString()}`);
1733
+ console.log('HELP IM QUOTING', `${finalHost}/permissions/quote?${queryParams.toString()}`);
1710
1734
  const response = await this.authFetch.fetch(`${finalHost}/permissions/quote?${queryParams.toString()}`, { method: 'GET' });
1711
- console.log("server response from getquote]", response);
1735
+ console.log('server response from getquote]', response);
1712
1736
  if (!response.ok) {
1713
1737
  const errorData = await response.json().catch(() => ({}));
1714
1738
  throw new Error(`Failed to get quote: HTTP ${response.status} - ${(_a = String(errorData.description)) !== null && _a !== void 0 ? _a : response.statusText}`);
@@ -1718,7 +1742,7 @@ class MessageBoxClient {
1718
1742
  throw new Error(description !== null && description !== void 0 ? description : 'Failed to get quote');
1719
1743
  }
1720
1744
  const deliveryAgentIdentityKey = response.headers.get('x-bsv-auth-identity-key');
1721
- console.log("deliveryAgentIdentityKey", deliveryAgentIdentityKey);
1745
+ console.log('deliveryAgentIdentityKey', deliveryAgentIdentityKey);
1722
1746
  if (deliveryAgentIdentityKey == null) {
1723
1747
  throw new Error('Failed to get quote: Delivery agent did not provide their identity key');
1724
1748
  }
@@ -1734,14 +1758,18 @@ class MessageBoxClient {
1734
1758
  throw new Error('At least one recipient is required.');
1735
1759
  }
1736
1760
  Logger.log('[MB CLIENT] Getting messageBox quotes (multi)...');
1737
- console.log("[MB CLIENT] Getting messageBox quotes (multi)...");
1761
+ console.log('[MB CLIENT] Getting messageBox quotes (multi)...');
1738
1762
  // Resolve host per recipient (unless caller forces overrideHost)
1739
1763
  // Group recipients by host so we call each overlay once.
1740
1764
  const hostGroups = new Map();
1741
- for (const r of recipients) {
1742
- const host = overrideHost !== null && overrideHost !== void 0 ? overrideHost : await this.resolveHostForRecipient(r);
1765
+ const resolvedHosts = overrideHost != null
1766
+ ? recipients.map(() => overrideHost)
1767
+ : await this.mapWithConcurrency(recipients, 8, async (r) => await this.resolveHostForRecipient(r));
1768
+ for (let i = 0; i < recipients.length; i++) {
1769
+ const r = recipients[i];
1770
+ const host = resolvedHosts[i];
1743
1771
  const list = hostGroups.get(host);
1744
- if (list)
1772
+ if (list != null)
1745
1773
  list.push(r);
1746
1774
  else
1747
1775
  hostGroups.set(host, [r]);
@@ -1827,7 +1855,7 @@ class MessageBoxClient {
1827
1855
  }
1828
1856
  };
1829
1857
  // Run all host groups (in parallel, but you can limit if needed)
1830
- await Promise.all(Array.from(hostGroups.entries()).map(([host, group]) => fetchGroup(host, group)));
1858
+ await Promise.all(Array.from(hostGroups.entries()).map(async ([host, group]) => await fetchGroup(host, group)));
1831
1859
  return {
1832
1860
  quotesByRecipient,
1833
1861
  totals: {
@@ -2243,7 +2271,7 @@ class MessageBoxClient {
2243
2271
  // figure out the per-request delivery fee (take it from any quoted recipient)
2244
2272
  const deliveryFeeOnce = (_a = recipients.reduce((acc, r) => {
2245
2273
  const q = perRecipientQuotes.get(r);
2246
- return q ? (acc !== null && acc !== void 0 ? acc : q.deliveryFee) : acc;
2274
+ return (q != null) ? (acc !== null && acc !== void 0 ? acc : q.deliveryFee) : acc;
2247
2275
  }, undefined)) !== null && _a !== void 0 ? _a : 0;
2248
2276
  const senderIdentityKey = await this.getIdentityKey();
2249
2277
  let outputIndex = 0;
@@ -2278,7 +2306,7 @@ class MessageBoxClient {
2278
2306
  const anyoneIdKey = (await anyoneWallet.getPublicKey({ identityKey: true })).publicKey;
2279
2307
  for (const r of recipients) {
2280
2308
  const q = perRecipientQuotes.get(r);
2281
- if (!q || q.recipientFee <= 0)
2309
+ if ((q == null) || q.recipientFee <= 0)
2282
2310
  continue;
2283
2311
  const derivationPrefix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
2284
2312
  const derivationSuffix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
@@ -2313,7 +2341,7 @@ class MessageBoxClient {
2313
2341
  outputs: createActionOutputs,
2314
2342
  options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
2315
2343
  }, this.originator);
2316
- if (!tx)
2344
+ if (tx == null)
2317
2345
  throw new Error('Failed to create payment transaction');
2318
2346
  return { tx, outputs, description };
2319
2347
  }