@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.
- package/dist/cjs/package.json +13 -5
- package/dist/cjs/src/MessageBoxClient.js +105 -77
- package/dist/cjs/src/MessageBoxClient.js.map +1 -1
- package/dist/cjs/src/PeerPayClient.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/MessageBoxClient.js +105 -78
- package/dist/esm/src/MessageBoxClient.js.map +1 -1
- package/dist/esm/src/PeerPayClient.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/MessageBoxClient.d.ts +1 -0
- package/dist/types/src/MessageBoxClient.d.ts.map +1 -1
- package/dist/types/src/PeerPayClient.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/package.json +13 -5
- package/src/MessageBoxClient.ts +406 -368
- package/src/PeerPayClient.ts +10 -10
package/dist/cjs/package.json
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsv/message-box-client",
|
|
3
|
-
"version": "
|
|
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": "^
|
|
39
|
-
"@bsv/payment-express-middleware": "^
|
|
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": "^
|
|
67
|
-
"@bsv/sdk": "^
|
|
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
|
|
973
|
-
|
|
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:
|
|
976
|
+
data: bodyBytes,
|
|
976
977
|
protocolID: [1, 'messagebox'],
|
|
977
978
|
keyID: '1',
|
|
978
979
|
counterparty: r
|
|
979
980
|
}, this.originator);
|
|
980
|
-
|
|
981
|
-
|
|
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
|
|
1022
|
-
|
|
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
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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.
|
|
1313
|
+
Logger.warn('[MB CLIENT] Recipient payment internalization was not accepted');
|
|
1316
1314
|
}
|
|
1317
1315
|
}
|
|
1318
|
-
|
|
1319
|
-
Logger.
|
|
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
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1742
|
-
|
|
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 (
|
|
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 (
|
|
2344
|
+
if (tx == null)
|
|
2317
2345
|
throw new Error('Failed to create payment transaction');
|
|
2318
2346
|
return { tx, outputs, description };
|
|
2319
2347
|
}
|