@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/src/MessageBoxClient.ts
CHANGED
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
ListPermissionsParams,
|
|
54
54
|
GetQuoteParams,
|
|
55
55
|
SendListParams,
|
|
56
|
-
SendListResult
|
|
56
|
+
SendListResult
|
|
57
57
|
} from './types/permissions.js'
|
|
58
58
|
|
|
59
59
|
const DEFAULT_MAINNET_HOST = 'https://messagebox.babbage.systems'
|
|
@@ -1002,172 +1002,174 @@ export class MessageBoxClient {
|
|
|
1002
1002
|
}
|
|
1003
1003
|
}
|
|
1004
1004
|
|
|
1005
|
-
|
|
1006
1005
|
/**
|
|
1007
1006
|
* Multi-recipient sender. Uses the multi-quote route to:
|
|
1008
1007
|
* - identify blocked recipients
|
|
1009
1008
|
* - compute per-recipient payment
|
|
1010
1009
|
* Then sends to the allowed recipients with payment attached.
|
|
1011
1010
|
*/
|
|
1012
|
-
async sendMesagetoRecepients(
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
): Promise<SendListResult> {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
const { recipients, messageBox, body, skipEncryption } = params
|
|
1019
|
-
if (!Array.isArray(recipients) || recipients.length === 0) {
|
|
1020
|
-
throw new Error('You must provide at least one recipient!')
|
|
1021
|
-
}
|
|
1022
|
-
if (!messageBox || messageBox.trim() === '') {
|
|
1023
|
-
throw new Error('You must provide a messageBox to send this message into!')
|
|
1024
|
-
}
|
|
1025
|
-
if (body == null || (typeof body === 'string' && body.trim().length === 0)) {
|
|
1026
|
-
throw new Error('Every message must have a body!')
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// 1) Multi-quote for all recipients
|
|
1030
|
-
const quoteResponse = await this.getMessageBoxQuote({
|
|
1031
|
-
recipient: recipients,
|
|
1032
|
-
messageBox
|
|
1033
|
-
}, overrideHost) as MessageBoxMultiQuote
|
|
1034
|
-
|
|
1035
|
-
const quotesByRecipient = Array.isArray(quoteResponse?.quotesByRecipient)
|
|
1036
|
-
? quoteResponse.quotesByRecipient : []
|
|
1011
|
+
async sendMesagetoRecepients (
|
|
1012
|
+
params: SendListParams,
|
|
1013
|
+
overrideHost?: string
|
|
1014
|
+
): Promise<SendListResult> {
|
|
1015
|
+
await this.assertInitialized()
|
|
1037
1016
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1017
|
+
const { recipients, messageBox, body, skipEncryption } = params
|
|
1018
|
+
if (!Array.isArray(recipients) || recipients.length === 0) {
|
|
1019
|
+
throw new Error('You must provide at least one recipient!')
|
|
1020
|
+
}
|
|
1021
|
+
if (!messageBox || messageBox.trim() === '') {
|
|
1022
|
+
throw new Error('You must provide a messageBox to send this message into!')
|
|
1023
|
+
}
|
|
1024
|
+
if (body == null || (typeof body === 'string' && body.trim().length === 0)) {
|
|
1025
|
+
throw new Error('Every message must have a body!')
|
|
1026
|
+
}
|
|
1040
1027
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1028
|
+
// 1) Multi-quote for all recipients
|
|
1029
|
+
const quoteResponse = await this.getMessageBoxQuote({
|
|
1030
|
+
recipient: recipients,
|
|
1031
|
+
messageBox
|
|
1032
|
+
}, overrideHost) as MessageBoxMultiQuote
|
|
1033
|
+
|
|
1034
|
+
const quotesByRecipient = Array.isArray(quoteResponse?.quotesByRecipient)
|
|
1035
|
+
? quoteResponse.quotesByRecipient
|
|
1036
|
+
: []
|
|
1037
|
+
|
|
1038
|
+
const blocked = (quoteResponse?.blockedRecipients ?? [])
|
|
1039
|
+
const totals = quoteResponse?.totals
|
|
1040
|
+
|
|
1041
|
+
// 2) Filter allowed recipients
|
|
1042
|
+
const allowedRecipients = recipients.filter(r => !blocked.includes(r))
|
|
1043
|
+
if (allowedRecipients.length === 0) {
|
|
1044
|
+
return {
|
|
1045
|
+
status: 'error',
|
|
1046
|
+
description: `All ${recipients.length} recipients are blocked.`,
|
|
1047
|
+
sent: [],
|
|
1048
|
+
blocked,
|
|
1049
|
+
failed: recipients.map(r => ({ recipient: r, error: 'blocked' })),
|
|
1050
|
+
totals
|
|
1051
|
+
}
|
|
1051
1052
|
}
|
|
1052
|
-
}
|
|
1053
1053
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1054
|
+
// 3) Map recipient -> fees
|
|
1055
|
+
const perRecipientQuotes = new Map<string, { recipientFee: number, deliveryFee: number }>()
|
|
1056
|
+
for (const q of quotesByRecipient) {
|
|
1057
|
+
perRecipientQuotes.set(q.recipient, { recipientFee: q.recipientFee, deliveryFee: q.deliveryFee })
|
|
1058
|
+
}
|
|
1059
1059
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1060
|
+
// 4) One delivery agent only (batch goes to one server)
|
|
1061
|
+
const { deliveryAgentIdentityKeyByHost } = quoteResponse
|
|
1062
|
+
if (!deliveryAgentIdentityKeyByHost || Object.keys(deliveryAgentIdentityKeyByHost).length === 0) {
|
|
1063
|
+
throw new Error('Missing delivery agent identity keys in quote response.')
|
|
1064
|
+
}
|
|
1065
|
+
if (Object.keys(deliveryAgentIdentityKeyByHost).length > 1 && !overrideHost) {
|
|
1066
1066
|
// To keep the single-POST invariant, we require all recipients to share a host
|
|
1067
|
-
|
|
1068
|
-
|
|
1067
|
+
throw new Error('Recipients resolve to multiple hosts. Use overrideHost to force a single server or split by host.')
|
|
1068
|
+
}
|
|
1069
1069
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1070
|
+
// pick the host to POST to
|
|
1071
|
+
const finalHost = (overrideHost ?? await this.resolveHostForRecipient(allowedRecipients[0])).replace(/\/+$/, '')
|
|
1072
|
+
const singleDeliveryKey = deliveryAgentIdentityKeyByHost[finalHost] ??
|
|
1073
|
+
Object.values(deliveryAgentIdentityKeyByHost)[0]
|
|
1074
1074
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1075
|
+
if (!singleDeliveryKey) {
|
|
1076
|
+
throw new Error('Could not determine server delivery agent identity key.')
|
|
1077
|
+
}
|
|
1078
1078
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1079
|
+
// 5) Identity key (sender)
|
|
1080
|
+
if (!this.myIdentityKey) {
|
|
1081
|
+
const keyResult = await this.walletClient.getPublicKey({ identityKey: true }, this.originator)
|
|
1082
|
+
this.myIdentityKey = keyResult.publicKey
|
|
1083
|
+
}
|
|
1084
1084
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
}
|
|
1085
|
+
// 6) Build per-recipient messageIds (HMAC), same order as allowedRecipients
|
|
1086
|
+
const bodyBytes = Array.from(new TextEncoder().encode(JSON.stringify(body)))
|
|
1087
|
+
const messageIds: string[] = await this.mapWithConcurrency(allowedRecipients, 8, async (r) => {
|
|
1088
|
+
const hmac = await this.walletClient.createHmac({
|
|
1089
|
+
data: bodyBytes,
|
|
1090
|
+
protocolID: [1, 'messagebox'],
|
|
1091
|
+
keyID: '1',
|
|
1092
|
+
counterparty: r
|
|
1093
|
+
}, this.originator)
|
|
1094
|
+
return Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('')
|
|
1095
|
+
})
|
|
1097
1096
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1097
|
+
// 7) Body: for batch route the server expects a single shared body
|
|
1098
|
+
// NOTE: If you need per-recipient encryption, we must change the server payload shape.
|
|
1099
|
+
let finalBody: string
|
|
1100
|
+
if (skipEncryption === true) {
|
|
1101
|
+
finalBody = typeof body === 'string' ? body : JSON.stringify(body)
|
|
1102
|
+
} else {
|
|
1104
1103
|
// safest for now: send plaintext; the recipients can decrypt payload fields client-side if needed
|
|
1105
|
-
|
|
1106
|
-
|
|
1104
|
+
finalBody = typeof body === 'string' ? body : JSON.stringify(body)
|
|
1105
|
+
}
|
|
1107
1106
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
// 9) Single POST to /sendMessage with recipients[] + messageId[]
|
|
1116
|
-
const requestBody = {
|
|
1117
|
-
message: {
|
|
1118
|
-
recipients: allowedRecipients,
|
|
1119
|
-
messageBox,
|
|
1120
|
-
messageId: messageIds, // aligned by index with recipients
|
|
1121
|
-
body: finalBody
|
|
1122
|
-
},
|
|
1123
|
-
payment: paymentData
|
|
1124
|
-
}
|
|
1107
|
+
// 8) ONE batch payment with server output at index 0
|
|
1108
|
+
const paymentData = await this.createMessagePaymentBatch(
|
|
1109
|
+
allowedRecipients,
|
|
1110
|
+
perRecipientQuotes,
|
|
1111
|
+
singleDeliveryKey
|
|
1112
|
+
)
|
|
1125
1113
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1114
|
+
// 9) Single POST to /sendMessage with recipients[] + messageId[]
|
|
1115
|
+
const requestBody = {
|
|
1116
|
+
message: {
|
|
1117
|
+
recipients: allowedRecipients,
|
|
1118
|
+
messageBox,
|
|
1119
|
+
messageId: messageIds, // aligned by index with recipients
|
|
1120
|
+
body: finalBody
|
|
1121
|
+
},
|
|
1122
|
+
payment: paymentData
|
|
1123
|
+
}
|
|
1128
1124
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
method: 'POST',
|
|
1132
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1133
|
-
body: JSON.stringify(requestBody)
|
|
1134
|
-
})
|
|
1125
|
+
Logger.log('[MB CLIENT] Sending HTTP request to:', `${finalHost}/sendMessage`)
|
|
1126
|
+
Logger.log('[MB CLIENT] Request Body (batch):', JSON.stringify({ ...requestBody, payment: { ...paymentData, tx: '<omitted>' } }, null, 2))
|
|
1135
1127
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1128
|
+
try {
|
|
1129
|
+
const response = await this.authFetch.fetch(`${finalHost}/sendMessage`, {
|
|
1130
|
+
method: 'POST',
|
|
1131
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1132
|
+
body: JSON.stringify(requestBody)
|
|
1133
|
+
})
|
|
1141
1134
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1135
|
+
const parsed = await response.json().catch(() => ({} as any))
|
|
1136
|
+
if (!response.ok || parsed.status !== 'success') {
|
|
1137
|
+
const msg = !response.ok ? `HTTP ${response.status} - ${response.statusText}` : (parsed.description ?? 'Unknown server error')
|
|
1138
|
+
throw new Error(msg)
|
|
1139
|
+
}
|
|
1145
1140
|
|
|
1146
|
-
|
|
1147
|
-
sent
|
|
1148
|
-
:
|
|
1149
|
-
: 'error'
|
|
1141
|
+
// server returns { results: [{ recipient, messageId }] }
|
|
1142
|
+
const sent = Array.isArray(parsed.results) ? parsed.results : []
|
|
1143
|
+
const failed: Array<{ recipient: string, error: string }> = [] // handled server-side now
|
|
1150
1144
|
|
|
1151
|
-
|
|
1145
|
+
const status: SendListResult['status'] =
|
|
1146
|
+
sent.length === allowedRecipients.length
|
|
1147
|
+
? 'success'
|
|
1148
|
+
: sent.length > 0
|
|
1149
|
+
? 'partial'
|
|
1150
|
+
: 'error'
|
|
1151
|
+
|
|
1152
|
+
const description =
|
|
1152
1153
|
status === 'success'
|
|
1153
1154
|
? `Sent to ${sent.length} recipients.`
|
|
1154
1155
|
: status === 'partial'
|
|
1155
1156
|
? `Sent to ${sent.length} recipients; ${allowedRecipients.length - sent.length} failed; ${blocked.length} blocked.`
|
|
1156
1157
|
: `Failed to send to ${allowedRecipients.length} allowed recipients. ${blocked.length} blocked.`
|
|
1157
1158
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1159
|
+
return { status, description, sent, blocked, failed, totals }
|
|
1160
|
+
} catch (err) {
|
|
1161
|
+
const msg = err instanceof Error ? err.message : 'Unknown error'
|
|
1162
|
+
return {
|
|
1163
|
+
status: 'error',
|
|
1164
|
+
description: `Batch send failed: ${msg}`,
|
|
1165
|
+
sent: [],
|
|
1166
|
+
blocked,
|
|
1167
|
+
failed: allowedRecipients.map(r => ({ recipient: r, error: msg })),
|
|
1168
|
+
totals
|
|
1169
|
+
}
|
|
1168
1170
|
}
|
|
1169
1171
|
}
|
|
1170
|
-
|
|
1172
|
+
|
|
1171
1173
|
/**
|
|
1172
1174
|
* @method anointHost
|
|
1173
1175
|
* @async
|
|
@@ -1444,108 +1446,112 @@ async sendMesagetoRecepients(
|
|
|
1444
1446
|
|
|
1445
1447
|
const messages: PeerMessage[] = Array.from(dedupMap.values())
|
|
1446
1448
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1449
|
+
const parsed = messages.map(message => {
|
|
1450
|
+
const parsedBody: unknown =
|
|
1451
|
+
typeof message.body === 'string' ? this.tryParse(message.body) : message.body
|
|
1452
|
+
|
|
1453
|
+
let messageContent: any = parsedBody
|
|
1454
|
+
let paymentData: Payment | undefined
|
|
1455
|
+
|
|
1456
|
+
if (
|
|
1457
|
+
parsedBody != null &&
|
|
1458
|
+
typeof parsedBody === 'object' &&
|
|
1459
|
+
'message' in parsedBody
|
|
1460
|
+
) {
|
|
1461
|
+
const wrappedMessage = (parsedBody as any).message
|
|
1462
|
+
messageContent = typeof wrappedMessage === 'string'
|
|
1463
|
+
? this.tryParse(wrappedMessage)
|
|
1464
|
+
: wrappedMessage
|
|
1465
|
+
paymentData = (parsedBody as any).payment
|
|
1466
|
+
}
|
|
1451
1467
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1468
|
+
return { message, parsedBody, messageContent, paymentData }
|
|
1469
|
+
})
|
|
1454
1470
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1471
|
+
if (acceptPayments) {
|
|
1472
|
+
const paymentJobs = parsed
|
|
1473
|
+
.filter(p => p.paymentData?.tx != null && p.paymentData.outputs != null)
|
|
1474
|
+
|
|
1475
|
+
await this.mapWithConcurrency(paymentJobs, 2, async (p) => {
|
|
1476
|
+
try {
|
|
1477
|
+
Logger.log(
|
|
1478
|
+
`[MB CLIENT] Processing recipient payment in message from ${String(p.message.sender)}…`
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
const recipientOutputs = (p.paymentData as Payment).outputs.filter(
|
|
1482
|
+
output => output.protocol === 'wallet payment'
|
|
1483
|
+
)
|
|
1467
1484
|
|
|
1468
|
-
|
|
1469
|
-
if (acceptPayments && paymentData?.tx != null && paymentData.outputs != null) {
|
|
1470
|
-
try {
|
|
1485
|
+
if (recipientOutputs.length > 0) {
|
|
1471
1486
|
Logger.log(
|
|
1472
|
-
`[MB CLIENT]
|
|
1487
|
+
`[MB CLIENT] Internalizing ${recipientOutputs.length} recipient payment output(s)…`
|
|
1473
1488
|
)
|
|
1474
1489
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
)
|
|
1490
|
+
const internalizeResult = await this.walletClient.internalizeAction({
|
|
1491
|
+
tx: (p.paymentData as Payment).tx,
|
|
1492
|
+
outputs: recipientOutputs,
|
|
1493
|
+
description: (p.paymentData as Payment).description ?? 'MessageBox recipient payment'
|
|
1494
|
+
}, this.originator)
|
|
1480
1495
|
|
|
1481
|
-
if (
|
|
1496
|
+
if (internalizeResult.accepted) {
|
|
1482
1497
|
Logger.log(
|
|
1483
|
-
|
|
1498
|
+
'[MB CLIENT] Successfully internalized recipient payment'
|
|
1484
1499
|
)
|
|
1485
|
-
|
|
1486
|
-
const internalizeResult = await this.walletClient.internalizeAction({
|
|
1487
|
-
tx: paymentData.tx,
|
|
1488
|
-
outputs: recipientOutputs,
|
|
1489
|
-
description: paymentData.description ?? 'MessageBox recipient payment'
|
|
1490
|
-
}, this.originator)
|
|
1491
|
-
|
|
1492
|
-
if (internalizeResult.accepted) {
|
|
1493
|
-
Logger.log(
|
|
1494
|
-
'[MB CLIENT] Successfully internalized recipient payment'
|
|
1495
|
-
)
|
|
1496
|
-
} else {
|
|
1497
|
-
Logger.warn(
|
|
1498
|
-
'[MB CLIENT] Recipient payment internalization was not accepted'
|
|
1499
|
-
)
|
|
1500
|
-
}
|
|
1501
1500
|
} else {
|
|
1502
|
-
Logger.
|
|
1503
|
-
'[MB CLIENT]
|
|
1501
|
+
Logger.warn(
|
|
1502
|
+
'[MB CLIENT] Recipient payment internalization was not accepted'
|
|
1504
1503
|
)
|
|
1505
1504
|
}
|
|
1506
|
-
}
|
|
1507
|
-
Logger.
|
|
1508
|
-
'[MB CLIENT
|
|
1509
|
-
paymentError
|
|
1505
|
+
} else {
|
|
1506
|
+
Logger.log(
|
|
1507
|
+
'[MB CLIENT] No wallet payment outputs found in payment data'
|
|
1510
1508
|
)
|
|
1511
|
-
// Continue processing the message even if payment fails
|
|
1512
1509
|
}
|
|
1510
|
+
} catch (paymentError) {
|
|
1511
|
+
Logger.error(
|
|
1512
|
+
'[MB CLIENT ERROR] Failed to internalize recipient payment:',
|
|
1513
|
+
paymentError
|
|
1514
|
+
)
|
|
1513
1515
|
}
|
|
1516
|
+
return null
|
|
1517
|
+
})
|
|
1518
|
+
}
|
|
1514
1519
|
|
|
1515
|
-
|
|
1520
|
+
await this.mapWithConcurrency(parsed, 4, async (p) => {
|
|
1521
|
+
try {
|
|
1516
1522
|
if (
|
|
1517
|
-
messageContent != null &&
|
|
1518
|
-
typeof messageContent === 'object' &&
|
|
1519
|
-
typeof (messageContent).encryptedMessage === 'string'
|
|
1523
|
+
p.messageContent != null &&
|
|
1524
|
+
typeof p.messageContent === 'object' &&
|
|
1525
|
+
typeof (p.messageContent).encryptedMessage === 'string'
|
|
1520
1526
|
) {
|
|
1521
1527
|
Logger.log(
|
|
1522
|
-
`[MB CLIENT] Decrypting message from ${String(message.sender)}…`
|
|
1528
|
+
`[MB CLIENT] Decrypting message from ${String(p.message.sender)}…`
|
|
1523
1529
|
)
|
|
1524
1530
|
|
|
1525
1531
|
const decrypted = await this.walletClient.decrypt({
|
|
1526
1532
|
protocolID: [1, 'messagebox'],
|
|
1527
1533
|
keyID: '1',
|
|
1528
|
-
counterparty: message.sender,
|
|
1534
|
+
counterparty: p.message.sender,
|
|
1529
1535
|
ciphertext: Utils.toArray(
|
|
1530
|
-
messageContent.encryptedMessage,
|
|
1536
|
+
(p.messageContent).encryptedMessage,
|
|
1531
1537
|
'base64'
|
|
1532
1538
|
)
|
|
1533
1539
|
}, this.originator)
|
|
1534
1540
|
|
|
1535
1541
|
const decryptedText = Utils.toUTF8(decrypted.plaintext)
|
|
1536
|
-
message.body = this.tryParse(decryptedText)
|
|
1542
|
+
p.message.body = this.tryParse(decryptedText)
|
|
1537
1543
|
} else {
|
|
1538
|
-
|
|
1539
|
-
message.body = messageContent ?? parsedBody
|
|
1544
|
+
p.message.body = p.messageContent ?? p.parsedBody
|
|
1540
1545
|
}
|
|
1541
1546
|
} catch (err) {
|
|
1542
1547
|
Logger.error(
|
|
1543
1548
|
'[MB CLIENT ERROR] Failed to parse or decrypt message in list:',
|
|
1544
1549
|
err
|
|
1545
1550
|
)
|
|
1546
|
-
message.body = '[Error: Failed to decrypt or parse message]'
|
|
1551
|
+
p.message.body = '[Error: Failed to decrypt or parse message]'
|
|
1547
1552
|
}
|
|
1548
|
-
|
|
1553
|
+
return null
|
|
1554
|
+
})
|
|
1549
1555
|
|
|
1550
1556
|
// Sort newest‑first for a deterministic order
|
|
1551
1557
|
messages.sort(
|
|
@@ -1603,7 +1609,8 @@ async sendMesagetoRecepients(
|
|
|
1603
1609
|
return raw
|
|
1604
1610
|
}
|
|
1605
1611
|
}
|
|
1606
|
-
|
|
1612
|
+
|
|
1613
|
+
await this.mapWithConcurrency(messages, 4, async (message) => {
|
|
1607
1614
|
try {
|
|
1608
1615
|
const parsedBody: unknown =
|
|
1609
1616
|
typeof message.body === 'string' ? tryParse(message.body) : message.body
|
|
@@ -1613,13 +1620,11 @@ async sendMesagetoRecepients(
|
|
|
1613
1620
|
typeof parsedBody === 'object' &&
|
|
1614
1621
|
'message' in parsedBody
|
|
1615
1622
|
) {
|
|
1616
|
-
// Handle wrapped message format (with payment data)
|
|
1617
1623
|
const wrappedMessage = (parsedBody as any).message
|
|
1618
1624
|
messageContent = typeof wrappedMessage === 'string'
|
|
1619
1625
|
? tryParse(wrappedMessage)
|
|
1620
1626
|
: wrappedMessage
|
|
1621
1627
|
}
|
|
1622
|
-
// Handle message decryption
|
|
1623
1628
|
if (
|
|
1624
1629
|
messageContent != null &&
|
|
1625
1630
|
typeof messageContent === 'object' &&
|
|
@@ -1637,7 +1642,6 @@ async sendMesagetoRecepients(
|
|
|
1637
1642
|
const decryptedText = Utils.toUTF8(decrypted.plaintext)
|
|
1638
1643
|
message.body = tryParse(decryptedText)
|
|
1639
1644
|
} else {
|
|
1640
|
-
// For non-encrypted messages, use the processed content
|
|
1641
1645
|
message.body = messageContent ?? parsedBody
|
|
1642
1646
|
}
|
|
1643
1647
|
} catch (err) {
|
|
@@ -1647,7 +1651,8 @@ async sendMesagetoRecepients(
|
|
|
1647
1651
|
)
|
|
1648
1652
|
message.body = '[Error: Failed to decrypt or parse message]'
|
|
1649
1653
|
}
|
|
1650
|
-
|
|
1654
|
+
return null
|
|
1655
|
+
})
|
|
1651
1656
|
return messages
|
|
1652
1657
|
}
|
|
1653
1658
|
|
|
@@ -1676,6 +1681,33 @@ async sendMesagetoRecepients(
|
|
|
1676
1681
|
}
|
|
1677
1682
|
}
|
|
1678
1683
|
|
|
1684
|
+
private async mapWithConcurrency<T, R> (
|
|
1685
|
+
items: T[],
|
|
1686
|
+
limit: number,
|
|
1687
|
+
fn: (item: T, index: number) => Promise<R>
|
|
1688
|
+
): Promise<R[]> {
|
|
1689
|
+
if (items.length === 0) return []
|
|
1690
|
+
if (!Number.isFinite(limit) || limit >= items.length) {
|
|
1691
|
+
return await Promise.all(items.map(fn))
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
const workerCount = Math.max(1, Math.min(limit, items.length))
|
|
1695
|
+
const results: R[] = new Array(items.length)
|
|
1696
|
+
let nextIndex = 0
|
|
1697
|
+
|
|
1698
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
1699
|
+
while (true) {
|
|
1700
|
+
const currentIndex = nextIndex
|
|
1701
|
+
nextIndex++
|
|
1702
|
+
if (currentIndex >= items.length) return
|
|
1703
|
+
results[currentIndex] = await fn(items[currentIndex], currentIndex)
|
|
1704
|
+
}
|
|
1705
|
+
})
|
|
1706
|
+
|
|
1707
|
+
await Promise.all(workers)
|
|
1708
|
+
return results
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1679
1711
|
/**
|
|
1680
1712
|
* @method acknowledgeNotification
|
|
1681
1713
|
* @async
|
|
@@ -1961,172 +1993,178 @@ async sendMesagetoRecepients(
|
|
|
1961
1993
|
* messageBox: 'notifications'
|
|
1962
1994
|
* })
|
|
1963
1995
|
*/
|
|
1964
|
-
async getMessageBoxQuote(
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
): Promise<MessageBoxQuote | MessageBoxMultiQuote> {
|
|
1996
|
+
async getMessageBoxQuote (
|
|
1997
|
+
params: GetQuoteParams,
|
|
1998
|
+
overrideHost?: string
|
|
1999
|
+
): Promise<MessageBoxQuote | MessageBoxMultiQuote> {
|
|
1968
2000
|
// ---------- SINGLE RECIPIENT (back-compat) ----------
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2001
|
+
if (!Array.isArray(params.recipient)) {
|
|
2002
|
+
const finalHost = overrideHost ?? await this.resolveHostForRecipient(params.recipient)
|
|
2003
|
+
const queryParams = new URLSearchParams({
|
|
2004
|
+
recipient: params.recipient,
|
|
2005
|
+
messageBox: params.messageBox
|
|
2006
|
+
})
|
|
1975
2007
|
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2008
|
+
Logger.log('[MB CLIENT] Getting messageBox quote (single)...')
|
|
2009
|
+
console.log('HELP IM QUOTING', `${finalHost}/permissions/quote?${queryParams.toString()}`)
|
|
2010
|
+
const response = await this.authFetch.fetch(
|
|
1979
2011
|
`${finalHost}/permissions/quote?${queryParams.toString()}`,
|
|
1980
2012
|
{ method: 'GET' }
|
|
1981
|
-
)
|
|
1982
|
-
console.log("server response from getquote]",response)
|
|
1983
|
-
if (!response.ok) {
|
|
1984
|
-
const errorData = await response.json().catch(() => ({}))
|
|
1985
|
-
throw new Error(
|
|
1986
|
-
`Failed to get quote: HTTP ${response.status} - ${String(errorData.description) ?? response.statusText}`
|
|
1987
2013
|
)
|
|
1988
|
-
|
|
2014
|
+
console.log('server response from getquote]', response)
|
|
2015
|
+
if (!response.ok) {
|
|
2016
|
+
const errorData = await response.json().catch(() => ({}))
|
|
2017
|
+
throw new Error(
|
|
2018
|
+
`Failed to get quote: HTTP ${response.status} - ${String(errorData.description) ?? response.statusText}`
|
|
2019
|
+
)
|
|
2020
|
+
}
|
|
1989
2021
|
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2022
|
+
const { status, description, quote } = await response.json()
|
|
2023
|
+
if (status === 'error') {
|
|
2024
|
+
throw new Error(description ?? 'Failed to get quote')
|
|
2025
|
+
}
|
|
1994
2026
|
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2027
|
+
const deliveryAgentIdentityKey = response.headers.get('x-bsv-auth-identity-key')
|
|
2028
|
+
console.log('deliveryAgentIdentityKey', deliveryAgentIdentityKey)
|
|
2029
|
+
if (deliveryAgentIdentityKey == null) {
|
|
2030
|
+
throw new Error('Failed to get quote: Delivery agent did not provide their identity key')
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
return {
|
|
2034
|
+
recipientFee: quote.recipientFee,
|
|
2035
|
+
deliveryFee: quote.deliveryFee,
|
|
2036
|
+
deliveryAgentIdentityKey
|
|
2037
|
+
}
|
|
1999
2038
|
}
|
|
2000
2039
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2040
|
+
// ---------- MULTI RECIPIENTS ----------
|
|
2041
|
+
const recipients = params.recipient
|
|
2042
|
+
if (recipients.length === 0) {
|
|
2043
|
+
throw new Error('At least one recipient is required.')
|
|
2005
2044
|
}
|
|
2006
|
-
}
|
|
2007
2045
|
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2046
|
+
Logger.log('[MB CLIENT] Getting messageBox quotes (multi)...')
|
|
2047
|
+
console.log('[MB CLIENT] Getting messageBox quotes (multi)...')
|
|
2048
|
+
// Resolve host per recipient (unless caller forces overrideHost)
|
|
2049
|
+
// Group recipients by host so we call each overlay once.
|
|
2050
|
+
const hostGroups = new Map<string, PubKeyHex[]>()
|
|
2013
2051
|
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
// Group recipients by host so we call each overlay once.
|
|
2018
|
-
const hostGroups = new Map<string, PubKeyHex[]>()
|
|
2019
|
-
for (const r of recipients) {
|
|
2020
|
-
const host = overrideHost ?? await this.resolveHostForRecipient(r)
|
|
2021
|
-
const list = hostGroups.get(host)
|
|
2022
|
-
if (list) list.push(r)
|
|
2023
|
-
else hostGroups.set(host, [r])
|
|
2024
|
-
}
|
|
2052
|
+
const resolvedHosts = overrideHost != null
|
|
2053
|
+
? recipients.map(() => overrideHost)
|
|
2054
|
+
: await this.mapWithConcurrency(recipients, 8, async (r) => await this.resolveHostForRecipient(r))
|
|
2025
2055
|
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
}> = []
|
|
2034
|
-
const blockedRecipients: PubKeyHex[] = []
|
|
2056
|
+
for (let i = 0; i < recipients.length; i++) {
|
|
2057
|
+
const r = recipients[i]
|
|
2058
|
+
const host = resolvedHosts[i]
|
|
2059
|
+
const list = hostGroups.get(host)
|
|
2060
|
+
if (list != null) list.push(r)
|
|
2061
|
+
else hostGroups.set(host, [r])
|
|
2062
|
+
}
|
|
2035
2063
|
|
|
2036
|
-
|
|
2037
|
-
|
|
2064
|
+
const deliveryAgentIdentityKeyByHost: Record<string, string> = {}
|
|
2065
|
+
const quotesByRecipient: Array<{
|
|
2066
|
+
recipient: PubKeyHex
|
|
2067
|
+
messageBox: string
|
|
2068
|
+
deliveryFee: number
|
|
2069
|
+
recipientFee: number
|
|
2070
|
+
status: 'blocked' | 'always_allow' | 'payment_required'
|
|
2071
|
+
}> = []
|
|
2072
|
+
const blockedRecipients: PubKeyHex[] = []
|
|
2038
2073
|
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
const qp = new URLSearchParams()
|
|
2042
|
-
for (const r of groupRecipients) qp.append('recipient', r)
|
|
2043
|
-
qp.set('messageBox', params.messageBox)
|
|
2074
|
+
let totalDeliveryFees = 0
|
|
2075
|
+
let totalRecipientFees = 0
|
|
2044
2076
|
|
|
2045
|
-
|
|
2046
|
-
|
|
2077
|
+
// Helper to fetch one host group
|
|
2078
|
+
const fetchGroup = async (host: string, groupRecipients: PubKeyHex[]) => {
|
|
2079
|
+
const qp = new URLSearchParams()
|
|
2080
|
+
for (const r of groupRecipients) qp.append('recipient', r)
|
|
2081
|
+
qp.set('messageBox', params.messageBox)
|
|
2047
2082
|
|
|
2048
|
-
|
|
2083
|
+
const url = `${host}/permissions/quote?${qp.toString()}`
|
|
2084
|
+
Logger.log('[MB CLIENT] Multi-quote GET:', url)
|
|
2049
2085
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2086
|
+
const resp = await this.authFetch.fetch(url, { method: 'GET' })
|
|
2087
|
+
|
|
2088
|
+
if (!resp.ok) {
|
|
2089
|
+
const errorData = await resp.json().catch(() => ({}))
|
|
2090
|
+
throw new Error(
|
|
2053
2091
|
`Failed to get quote (host ${host}): HTTP ${resp.status} - ${String(errorData.description) ?? resp.statusText}`
|
|
2054
|
-
|
|
2055
|
-
|
|
2092
|
+
)
|
|
2093
|
+
}
|
|
2056
2094
|
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2095
|
+
const deliveryAgentKey = resp.headers.get('x-bsv-auth-identity-key')
|
|
2096
|
+
if (!deliveryAgentKey) {
|
|
2097
|
+
throw new Error(`Failed to get quote (host ${host}): missing delivery agent identity key`)
|
|
2098
|
+
}
|
|
2099
|
+
deliveryAgentIdentityKeyByHost[host] = deliveryAgentKey
|
|
2062
2100
|
|
|
2063
|
-
|
|
2101
|
+
const payload = await resp.json()
|
|
2064
2102
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2103
|
+
// Server supports both shapes. For multi we expect:
|
|
2104
|
+
// { quotesByRecipient, totals, blockedRecipients }
|
|
2105
|
+
if (Array.isArray(payload?.quotesByRecipient)) {
|
|
2068
2106
|
// merge quotes
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2107
|
+
for (const q of payload.quotesByRecipient) {
|
|
2108
|
+
quotesByRecipient.push({
|
|
2109
|
+
recipient: q.recipient,
|
|
2110
|
+
messageBox: q.messageBox,
|
|
2111
|
+
deliveryFee: q.deliveryFee,
|
|
2112
|
+
recipientFee: q.recipientFee,
|
|
2113
|
+
status: q.status
|
|
2114
|
+
})
|
|
2115
|
+
// aggregate client-side totals as well (in case we hit multiple hosts)
|
|
2116
|
+
totalDeliveryFees += q.deliveryFee
|
|
2117
|
+
if (q.recipientFee === -1) {
|
|
2118
|
+
if (!blockedRecipients.includes(q.recipient)) blockedRecipients.push(q.recipient)
|
|
2119
|
+
} else {
|
|
2120
|
+
totalRecipientFees += q.recipientFee
|
|
2121
|
+
}
|
|
2083
2122
|
}
|
|
2084
|
-
}
|
|
2085
2123
|
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2124
|
+
// Also merge server totals if present (they are per-host); we already aggregated above,
|
|
2125
|
+
// so we don’t need to use payload.totals except for sanity/logging.
|
|
2126
|
+
if (Array.isArray(payload?.blockedRecipients)) {
|
|
2127
|
+
for (const br of payload.blockedRecipients) {
|
|
2128
|
+
if (!blockedRecipients.includes(br)) blockedRecipients.push(br)
|
|
2129
|
+
}
|
|
2091
2130
|
}
|
|
2092
|
-
}
|
|
2093
|
-
} else if (payload?.quote) {
|
|
2131
|
+
} else if (payload?.quote) {
|
|
2094
2132
|
// Defensive: if an overlay still returns single-quote shape for multi (shouldn’t),
|
|
2095
2133
|
// we map it to each recipient in the group uniformly.
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2134
|
+
for (const r of groupRecipients) {
|
|
2135
|
+
const { deliveryFee, recipientFee } = payload.quote
|
|
2136
|
+
const status =
|
|
2099
2137
|
recipientFee === -1 ? 'blocked' : recipientFee === 0 ? 'always_allow' : 'payment_required'
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2138
|
+
quotesByRecipient.push({
|
|
2139
|
+
recipient: r,
|
|
2140
|
+
messageBox: params.messageBox,
|
|
2141
|
+
deliveryFee,
|
|
2142
|
+
recipientFee,
|
|
2143
|
+
status
|
|
2144
|
+
})
|
|
2145
|
+
totalDeliveryFees += deliveryFee
|
|
2146
|
+
if (recipientFee === -1) blockedRecipients.push(r)
|
|
2147
|
+
else totalRecipientFees += recipientFee
|
|
2148
|
+
}
|
|
2149
|
+
} else {
|
|
2150
|
+
throw new Error(`Unexpected quote response shape from host ${host}`)
|
|
2110
2151
|
}
|
|
2111
|
-
} else {
|
|
2112
|
-
throw new Error(`Unexpected quote response shape from host ${host}`)
|
|
2113
2152
|
}
|
|
2114
|
-
}
|
|
2115
2153
|
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2154
|
+
// Run all host groups (in parallel, but you can limit if needed)
|
|
2155
|
+
await Promise.all(Array.from(hostGroups.entries()).map(async ([host, group]) => await fetchGroup(host, group)))
|
|
2156
|
+
|
|
2157
|
+
return {
|
|
2158
|
+
quotesByRecipient,
|
|
2159
|
+
totals: {
|
|
2160
|
+
deliveryFees: totalDeliveryFees,
|
|
2161
|
+
recipientFees: totalRecipientFees,
|
|
2162
|
+
totalForPayableRecipients: totalDeliveryFees + totalRecipientFees
|
|
2163
|
+
},
|
|
2164
|
+
blockedRecipients,
|
|
2165
|
+
deliveryAgentIdentityKeyByHost
|
|
2166
|
+
}
|
|
2128
2167
|
}
|
|
2129
|
-
}
|
|
2130
2168
|
|
|
2131
2169
|
/**
|
|
2132
2170
|
* @method listMessageBoxPermissions
|
|
@@ -2291,31 +2329,31 @@ async getMessageBoxQuote(
|
|
|
2291
2329
|
* // Send with maximum payment limit for safety
|
|
2292
2330
|
* await client.sendNotification('03def456...', { title: 'Alert', body: 'Important update' }, 50)
|
|
2293
2331
|
*/
|
|
2294
|
-
async sendNotification(
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
): Promise<SendMessageResponse | SendListResult> {
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2332
|
+
async sendNotification (
|
|
2333
|
+
recipient: PubKeyHex | PubKeyHex[],
|
|
2334
|
+
body: string | object,
|
|
2335
|
+
overrideHost?: string
|
|
2336
|
+
): Promise<SendMessageResponse | SendListResult> {
|
|
2337
|
+
await this.assertInitialized()
|
|
2338
|
+
|
|
2339
|
+
// Single recipient → keep original flow
|
|
2340
|
+
if (!Array.isArray(recipient)) {
|
|
2341
|
+
return await this.sendMessage({
|
|
2342
|
+
recipient,
|
|
2343
|
+
messageBox: 'notifications',
|
|
2344
|
+
body,
|
|
2345
|
+
checkPermissions: true
|
|
2346
|
+
}, overrideHost)
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
// Multiple recipients → new flow
|
|
2350
|
+
return await this.sendMesagetoRecepients({
|
|
2351
|
+
recipients: recipient,
|
|
2305
2352
|
messageBox: 'notifications',
|
|
2306
|
-
body
|
|
2307
|
-
checkPermissions: true
|
|
2353
|
+
body
|
|
2308
2354
|
}, overrideHost)
|
|
2309
2355
|
}
|
|
2310
2356
|
|
|
2311
|
-
// Multiple recipients → new flow
|
|
2312
|
-
return await this.sendMesagetoRecepients({
|
|
2313
|
-
recipients: recipient,
|
|
2314
|
-
messageBox: 'notifications',
|
|
2315
|
-
body
|
|
2316
|
-
}, overrideHost)
|
|
2317
|
-
}
|
|
2318
|
-
|
|
2319
2357
|
/**
|
|
2320
2358
|
* Register a device for FCM push notifications.
|
|
2321
2359
|
*
|
|
@@ -2579,22 +2617,22 @@ async getMessageBoxQuote(
|
|
|
2579
2617
|
}
|
|
2580
2618
|
}
|
|
2581
2619
|
|
|
2582
|
-
private async createMessagePaymentBatch(
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2620
|
+
private async createMessagePaymentBatch (
|
|
2621
|
+
recipients: string[],
|
|
2622
|
+
perRecipientQuotes: Map<string, { recipientFee: number, deliveryFee: number }>,
|
|
2623
|
+
// server (delivery agent) identity key to pay the delivery fee to
|
|
2624
|
+
serverIdentityKey: string,
|
|
2625
|
+
description = 'MessageBox delivery payment (batch)'
|
|
2588
2626
|
): Promise<Payment> {
|
|
2589
2627
|
const outputs: InternalizeOutput[] = []
|
|
2590
2628
|
const createActionOutputs: CreateActionOutput[] = []
|
|
2591
2629
|
|
|
2592
2630
|
// figure out the per-request delivery fee (take it from any quoted recipient)
|
|
2593
2631
|
const deliveryFeeOnce =
|
|
2594
|
-
recipients.reduce((acc, r) => {
|
|
2632
|
+
recipients.reduce<number | undefined>((acc, r) => {
|
|
2595
2633
|
const q = perRecipientQuotes.get(r)
|
|
2596
|
-
return q ? (acc ?? q.deliveryFee) : acc
|
|
2597
|
-
}, undefined
|
|
2634
|
+
return (q != null) ? (acc ?? q.deliveryFee) : acc
|
|
2635
|
+
}, undefined) ?? 0
|
|
2598
2636
|
|
|
2599
2637
|
const senderIdentityKey = await this.getIdentityKey()
|
|
2600
2638
|
let outputIndex = 0
|
|
@@ -2636,7 +2674,7 @@ async getMessageBoxQuote(
|
|
|
2636
2674
|
|
|
2637
2675
|
for (const r of recipients) {
|
|
2638
2676
|
const q = perRecipientQuotes.get(r)
|
|
2639
|
-
if (
|
|
2677
|
+
if ((q == null) || q.recipientFee <= 0) continue
|
|
2640
2678
|
|
|
2641
2679
|
const derivationPrefix = Utils.toBase64(Random(32))
|
|
2642
2680
|
const derivationSuffix = Utils.toBase64(Random(32))
|
|
@@ -2677,7 +2715,7 @@ async getMessageBoxQuote(
|
|
|
2677
2715
|
options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
|
|
2678
2716
|
}, this.originator)
|
|
2679
2717
|
|
|
2680
|
-
if (
|
|
2718
|
+
if (tx == null) throw new Error('Failed to create payment transaction')
|
|
2681
2719
|
|
|
2682
2720
|
return { tx, outputs, description }
|
|
2683
2721
|
}
|