@bopen-io/messagebox-server 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +372 -0
  2. package/knexfile.d.ts +5 -0
  3. package/knexfile.ts +29 -0
  4. package/out/knexfile.js +25 -0
  5. package/out/knexfile.js.map +1 -0
  6. package/out/src/app.js +160 -0
  7. package/out/src/app.js.map +1 -0
  8. package/out/src/config/firebase.js +150 -0
  9. package/out/src/config/firebase.js.map +1 -0
  10. package/out/src/index.js +295 -0
  11. package/out/src/index.js.map +1 -0
  12. package/out/src/migrations/2022-12-28-001-initial-migration.js +28 -0
  13. package/out/src/migrations/2022-12-28-001-initial-migration.js.map +1 -0
  14. package/out/src/migrations/2023-01-17-messages-update.js +11 -0
  15. package/out/src/migrations/2023-01-17-messages-update.js.map +1 -0
  16. package/out/src/migrations/2024-03-05-001-messageID-upgrade.js +21 -0
  17. package/out/src/migrations/2024-03-05-001-messageID-upgrade.js.map +1 -0
  18. package/out/src/migrations/2025-01-31-001-notification-permissions.js +59 -0
  19. package/out/src/migrations/2025-01-31-001-notification-permissions.js.map +1 -0
  20. package/out/src/migrations/2025-01-31-002-device-registrations.js +24 -0
  21. package/out/src/migrations/2025-01-31-002-device-registrations.js.map +1 -0
  22. package/out/src/routes/__tests/acknowledgeMessage.test.js +129 -0
  23. package/out/src/routes/__tests/acknowledgeMessage.test.js.map +1 -0
  24. package/out/src/routes/__tests/listMessages.test.js +211 -0
  25. package/out/src/routes/__tests/listMessages.test.js.map +1 -0
  26. package/out/src/routes/__tests/sendMessage.test.js +307 -0
  27. package/out/src/routes/__tests/sendMessage.test.js.map +1 -0
  28. package/out/src/routes/acknowledgeMessage.js +144 -0
  29. package/out/src/routes/acknowledgeMessage.js.map +1 -0
  30. package/out/src/routes/index.js +17 -0
  31. package/out/src/routes/index.js.map +1 -0
  32. package/out/src/routes/listDevices.js +74 -0
  33. package/out/src/routes/listDevices.js.map +1 -0
  34. package/out/src/routes/listMessages.js +186 -0
  35. package/out/src/routes/listMessages.js.map +1 -0
  36. package/out/src/routes/permissions/getPermission.js +149 -0
  37. package/out/src/routes/permissions/getPermission.js.map +1 -0
  38. package/out/src/routes/permissions/getQuote.js +165 -0
  39. package/out/src/routes/permissions/getQuote.js.map +1 -0
  40. package/out/src/routes/permissions/index.js +12 -0
  41. package/out/src/routes/permissions/index.js.map +1 -0
  42. package/out/src/routes/permissions/listPermissions.js +190 -0
  43. package/out/src/routes/permissions/listPermissions.js.map +1 -0
  44. package/out/src/routes/permissions/setPermission.js +137 -0
  45. package/out/src/routes/permissions/setPermission.js.map +1 -0
  46. package/out/src/routes/registerDevice.js +150 -0
  47. package/out/src/routes/registerDevice.js.map +1 -0
  48. package/out/src/routes/sendMessage.js +451 -0
  49. package/out/src/routes/sendMessage.js.map +1 -0
  50. package/out/src/swagger.js +49 -0
  51. package/out/src/swagger.js.map +1 -0
  52. package/out/src/types/messagePermissions.js +2 -0
  53. package/out/src/types/messagePermissions.js.map +1 -0
  54. package/out/src/types/notifications.js +2 -0
  55. package/out/src/types/notifications.js.map +1 -0
  56. package/out/src/utils/logger.js +24 -0
  57. package/out/src/utils/logger.js.map +1 -0
  58. package/out/src/utils/messagePermissions.js +126 -0
  59. package/out/src/utils/messagePermissions.js.map +1 -0
  60. package/out/src/utils/sendFCMNotification.js +121 -0
  61. package/out/src/utils/sendFCMNotification.js.map +1 -0
  62. package/out/tsconfig.tsbuildinfo +1 -0
  63. package/package.json +88 -0
  64. package/src/migrations/2022-12-28-001-initial-migration.ts +31 -0
  65. package/src/migrations/2023-01-17-messages-update.ts +13 -0
  66. package/src/migrations/2024-03-05-001-messageID-upgrade.ts +25 -0
  67. package/src/migrations/2025-01-31-001-notification-permissions.ts +68 -0
  68. package/src/migrations/2025-01-31-002-device-registrations.ts +28 -0
  69. package/src/templates/documentation.ejs +62 -0
@@ -0,0 +1,150 @@
1
+ import knexConfig from '../../knexfile.js';
2
+ import * as knexLib from 'knex';
3
+ import { Logger } from '../utils/logger.js';
4
+ // Determine the environment (default to development)
5
+ const { NODE_ENV = 'development' } = process.env;
6
+ /**
7
+ * Knex instance connected based on environment (development, production, or staging).
8
+ */
9
+ const knex = knexLib.default?.(NODE_ENV === 'production' || NODE_ENV === 'staging'
10
+ ? knexConfig.production
11
+ : knexConfig.development) ?? knexLib(NODE_ENV === 'production' || NODE_ENV === 'staging'
12
+ ? knexConfig.production
13
+ : knexConfig.development);
14
+ /**
15
+ * @swagger
16
+ * /registerDevice:
17
+ * post:
18
+ * summary: Register device for push notifications
19
+ * description: Register a device's FCM token for receiving push notifications
20
+ * tags:
21
+ * - Device
22
+ * requestBody:
23
+ * required: true
24
+ * content:
25
+ * application/json:
26
+ * schema:
27
+ * type: object
28
+ * required:
29
+ * - fcmToken
30
+ * properties:
31
+ * fcmToken:
32
+ * type: string
33
+ * description: Firebase Cloud Messaging token
34
+ * deviceId:
35
+ * type: string
36
+ * description: Optional device identifier
37
+ * platform:
38
+ * type: string
39
+ * description: Device platform (ios, android, web)
40
+ * enum: [ios, android, web]
41
+ * responses:
42
+ * 200:
43
+ * description: Device registered successfully
44
+ * content:
45
+ * application/json:
46
+ * schema:
47
+ * type: object
48
+ * properties:
49
+ * status:
50
+ * type: string
51
+ * example: success
52
+ * message:
53
+ * type: string
54
+ * example: Device registered successfully
55
+ * deviceId:
56
+ * type: integer
57
+ * description: Database ID of the registered device
58
+ * 400:
59
+ * description: Invalid request parameters
60
+ * 401:
61
+ * description: Authentication required
62
+ * 500:
63
+ * description: Internal server error
64
+ */
65
+ export default {
66
+ type: 'post',
67
+ path: '/registerDevice',
68
+ func: async (req, res) => {
69
+ try {
70
+ Logger.log('[DEBUG] Processing device registration request');
71
+ // Validate authentication
72
+ const identityKey = req.auth?.identityKey;
73
+ if (identityKey == null) {
74
+ Logger.log('[DEBUG] Authentication required for device registration');
75
+ return res.status(401).json({
76
+ status: 'error',
77
+ code: 'ERR_AUTHENTICATION_REQUIRED',
78
+ description: 'Authentication required.'
79
+ });
80
+ }
81
+ const { fcmToken, deviceId, platform } = req.body;
82
+ // Validate required fields
83
+ if (fcmToken == null || typeof fcmToken !== 'string' || fcmToken.trim() === '') {
84
+ Logger.log('[DEBUG] Invalid FCM token provided');
85
+ return res.status(400).json({
86
+ status: 'error',
87
+ code: 'ERR_INVALID_FCM_TOKEN',
88
+ description: 'fcmToken is required and must be a non-empty string.'
89
+ });
90
+ }
91
+ // Validate platform if provided
92
+ const validPlatforms = ['ios', 'android', 'web'];
93
+ if (platform != null && !validPlatforms.includes(platform)) {
94
+ Logger.log('[DEBUG] Invalid platform provided');
95
+ return res.status(400).json({
96
+ status: 'error',
97
+ code: 'ERR_INVALID_PLATFORM',
98
+ description: 'platform must be one of: ios, android, web'
99
+ });
100
+ }
101
+ try {
102
+ // Insert or update device registration
103
+ const now = new Date();
104
+ const [deviceRegistrationId] = await knex('device_registrations')
105
+ .insert({
106
+ identity_key: identityKey,
107
+ fcm_token: fcmToken.trim(),
108
+ device_id: deviceId?.trim() ?? null,
109
+ platform: platform ?? null,
110
+ created_at: now,
111
+ updated_at: now,
112
+ active: true,
113
+ last_used: now
114
+ })
115
+ .onConflict('fcm_token')
116
+ .merge({
117
+ identity_key: identityKey, // Update identity key in case token was reassigned
118
+ device_id: deviceId?.trim() ?? null,
119
+ platform: platform ?? null,
120
+ updated_at: now,
121
+ active: true,
122
+ last_used: now
123
+ });
124
+ Logger.log(`[DEBUG] Device registered successfully: ${identityKey} with token ending in ...${fcmToken.slice(-10)}`);
125
+ return res.status(200).json({
126
+ status: 'success',
127
+ message: 'Device registered successfully for push notifications',
128
+ deviceId: deviceRegistrationId
129
+ });
130
+ }
131
+ catch (dbError) {
132
+ Logger.error('[ERROR] Database error during device registration:', dbError);
133
+ return res.status(500).json({
134
+ status: 'error',
135
+ code: 'ERR_DATABASE_ERROR',
136
+ description: 'Failed to register device.'
137
+ });
138
+ }
139
+ }
140
+ catch (error) {
141
+ Logger.error('[ERROR] Internal Server Error in registerDevice:', error);
142
+ return res.status(500).json({
143
+ status: 'error',
144
+ code: 'ERR_INTERNAL',
145
+ description: 'An internal error has occurred.'
146
+ });
147
+ }
148
+ }
149
+ };
150
+ //# sourceMappingURL=registerDevice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerDevice.js","sourceRoot":"","sources":["../../../src/routes/registerDevice.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,451 @@
1
+ /**
2
+ * @file sendMessage.ts
3
+ * @description
4
+ * Route handler to send a message to another identity's messageBox.
5
+ * This route is used for P2P communication in the MessageBox system.
6
+ *
7
+ * It handles:
8
+ * - Validation of message structure
9
+ * - Validation of the recipient public key
10
+ * - MessageBox creation if one doesn't exist
11
+ * - Insertion of the message into the database
12
+ * - Deduplication based on messageId
13
+ *
14
+ */
15
+ import knexConfig from '../../knexfile.js';
16
+ import * as knexLib from 'knex';
17
+ import { PublicKey, } from '@bsv/sdk';
18
+ import { Logger } from '../utils/logger.js';
19
+ import { sendFCMNotification } from '../utils/sendFCMNotification.js';
20
+ import { getRecipientFee, getServerDeliveryFee, shouldUseFCMDelivery } from '../utils/messagePermissions.js';
21
+ // Determine the environment (default to development)
22
+ const { NODE_ENV = 'development', SERVER_PRIVATE_KEY } = process.env;
23
+ /**
24
+ * Knex instance connected based on environment (development, production, or staging).
25
+ */
26
+ const knex = knexLib.default?.(NODE_ENV === 'production' || NODE_ENV === 'staging'
27
+ ? knexConfig.production
28
+ : knexConfig.development) ??
29
+ knexLib(NODE_ENV === 'production' || NODE_ENV === 'staging'
30
+ ? knexConfig.production
31
+ : knexConfig.development);
32
+ // Validate critical server-side secret
33
+ if (SERVER_PRIVATE_KEY == null || SERVER_PRIVATE_KEY.trim() === '') {
34
+ throw new Error('SERVER_PRIVATE_KEY is not defined in the environment variables.');
35
+ }
36
+ /**
37
+ * @function calculateMessagePrice
38
+ * @description Determines the price (in satoshis) to send a message, optionally with priority.
39
+ */
40
+ export function calculateMessagePrice(message, priority = false) {
41
+ const basePrice = 2; // Base fee in satoshis
42
+ const sizeFactor = Math.ceil(Buffer.byteLength(message, 'utf8') / 1024) * 3; // Satoshis per KB
43
+ return basePrice + sizeFactor;
44
+ }
45
+ /**
46
+ * @openapi
47
+ * /sendMessage:
48
+ * post:
49
+ * summary: Send a message to a recipient’s message box
50
+ * description: |
51
+ * Inserts a message into the target recipient’s message box on the server.
52
+ * The recipient, message box name, and message ID must be provided.
53
+ * tags:
54
+ * - Message
55
+ * requestBody:
56
+ * required: true
57
+ * content:
58
+ * application/json:
59
+ * schema:
60
+ * type: object
61
+ * properties:
62
+ * message:
63
+ * type: object
64
+ * required:
65
+ * - recipient
66
+ * - messageBox
67
+ * - messageId
68
+ * - body
69
+ * properties:
70
+ * recipient:
71
+ * type: string
72
+ * description: Identity key of the recipient
73
+ * messageBox:
74
+ * type: string
75
+ * description: The name of the recipient's message box
76
+ * messageId:
77
+ * type: string
78
+ * description: Unique identifier for the message (usually an HMAC)
79
+ * body:
80
+ * oneOf:
81
+ * - type: string
82
+ * - type: object
83
+ * description: The message content
84
+ * responses:
85
+ * 200:
86
+ * description: Message stored successfully
87
+ * content:
88
+ * application/json:
89
+ * schema:
90
+ * type: object
91
+ * properties:
92
+ * status:
93
+ * type: string
94
+ * example: success
95
+ * messageId:
96
+ * type: string
97
+ * message:
98
+ * type: string
99
+ * 400:
100
+ * description: Invalid request or duplicate message
101
+ * 500:
102
+ * description: Internal server error
103
+ */
104
+ /**
105
+ * @exports
106
+ * Express-compatible route definition for `/sendMessage`, used to send messages to other users.
107
+ * Contains metadata for auto-generation of route documentation and Swagger/OpenAPI integration.
108
+ */
109
+ export default {
110
+ type: 'post',
111
+ path: '/sendMessage',
112
+ knex,
113
+ summary: "Use this route to send a message to a recipient's message box.",
114
+ parameters: {
115
+ message: {
116
+ recipient: '028d37b941208cd6b8a4c28288eda5f2f16c2b3ab0fcb6d13c18b47fe37b971fc1',
117
+ messageBox: 'payment_inbox',
118
+ messageId: 'xyz123',
119
+ body: '{}'
120
+ }
121
+ },
122
+ exampleResponse: { status: 'success' },
123
+ func: async (req, res) => {
124
+ Logger.log('[DEBUG] Processing /sendMessage request...');
125
+ Logger.log('[DEBUG] Request Headers:', JSON.stringify(req.headers, null, 2));
126
+ const senderKey = req.auth?.identityKey;
127
+ if (senderKey == null) {
128
+ return res.status(401).json({
129
+ status: 'error',
130
+ code: 'ERR_AUTH_REQUIRED',
131
+ description: 'Authentication required'
132
+ });
133
+ }
134
+ try {
135
+ const { message, payment } = req.body;
136
+ console.log('Received message send request from:', message, payment);
137
+ if (message == null) {
138
+ Logger.error('[ERROR] No message provided in request body!');
139
+ return res.status(400).json({
140
+ status: 'error',
141
+ code: 'ERR_MESSAGE_REQUIRED',
142
+ description: 'Please provide a valid message to send!'
143
+ });
144
+ }
145
+ if (typeof message.messageBox !== 'string' || message.messageBox.trim() === '') {
146
+ return res.status(400).json({ status: 'error', code: 'ERR_INVALID_MESSAGEBOX', description: 'Invalid message box.' });
147
+ }
148
+ if ((typeof message.body !== 'string' && (typeof message.body !== 'object' || message.body === null)) ||
149
+ (typeof message.body === 'string' && message.body.trim() === '')) {
150
+ return res.status(400).json({ status: 'error', code: 'ERR_INVALID_MESSAGE_BODY', description: 'Invalid message body.' });
151
+ }
152
+ // ---------- Back-compat normalization ----------
153
+ // Accept message.recipients (array) or message.recipient (string|array)
154
+ const recipientsRaw = message.recipients ?? message.recipient;
155
+ if (recipientsRaw == null) {
156
+ return res.status(400).json({
157
+ status: 'error',
158
+ code: 'ERR_RECIPIENT_REQUIRED',
159
+ description: 'Missing recipient(s). Provide "recipient" or "recipients".'
160
+ });
161
+ }
162
+ const recipients = Array.isArray(recipientsRaw)
163
+ ? recipientsRaw
164
+ : [recipientsRaw];
165
+ const messageIdRaw = message.messageId;
166
+ if (messageIdRaw == null) {
167
+ return res.status(400).json({
168
+ status: 'error',
169
+ code: 'ERR_MESSAGEID_REQUIRED',
170
+ description: 'Missing messageId.'
171
+ });
172
+ }
173
+ const messageIds = Array.isArray(messageIdRaw)
174
+ ? messageIdRaw
175
+ : [messageIdRaw];
176
+ // If multiple recipients but only one messageId provided, fail clearly (avoid accidental reuse)
177
+ if (recipients.length > 1 && messageIds.length === 1) {
178
+ return res.status(400).json({
179
+ status: 'error',
180
+ code: 'ERR_MESSAGEID_COUNT_MISMATCH',
181
+ description: `Provided 1 messageId for ${recipients.length} recipients. Provide one messageId per recipient (same order).`
182
+ });
183
+ }
184
+ if (messageIds.length !== recipients.length) {
185
+ return res.status(400).json({
186
+ status: 'error',
187
+ code: 'ERR_MESSAGEID_COUNT_MISMATCH',
188
+ description: `Recipients (${recipients.length}) and messageId count (${messageIds.length}) must match.`
189
+ });
190
+ }
191
+ // Validate each messageId
192
+ for (const id of messageIds) {
193
+ if (typeof id !== 'string' || id.trim() === '') {
194
+ return res.status(400).json({ status: 'error', code: 'ERR_INVALID_MESSAGEID', description: 'Each messageId must be a non-empty string.' });
195
+ }
196
+ }
197
+ // Validate recipient keys & build map(recipient -> messageId)
198
+ const recipientsTrimmed = recipients.map(r => String(r).trim());
199
+ const msgIdByRecipient = new Map();
200
+ for (let i = 0; i < recipientsTrimmed.length; i++) {
201
+ const r = recipientsTrimmed[i];
202
+ try {
203
+ PublicKey.fromString(r);
204
+ }
205
+ catch {
206
+ return res.status(400).json({
207
+ status: 'error',
208
+ code: 'ERR_INVALID_RECIPIENT_KEY',
209
+ description: `Invalid recipient key: ${r}`
210
+ });
211
+ }
212
+ msgIdByRecipient.set(r, messageIds[i]);
213
+ }
214
+ // Ensure messageBox exists for each recipient
215
+ const boxType = message.messageBox.trim();
216
+ for (const r of recipientsTrimmed) {
217
+ const existing = await knex('messageBox').where({ identityKey: r, type: boxType }).first();
218
+ if (!existing) {
219
+ await knex('messageBox').insert({
220
+ identityKey: r, type: boxType, created_at: new Date(), updated_at: new Date()
221
+ });
222
+ }
223
+ }
224
+ // ---------- Fee evaluation ----------
225
+ const deliveryFeeOnce = await getServerDeliveryFee(boxType);
226
+ const feeRows = [];
227
+ for (const r of recipientsTrimmed) {
228
+ const rf = await getRecipientFee(r, senderKey, boxType); // -1 = blocked; 0 = allow; >0 = sats required
229
+ if (rf === -1)
230
+ feeRows.push({ recipient: r, recipientFee: rf, allowed: false, blockedReason: `Messages to ${r} are blocked` });
231
+ else
232
+ feeRows.push({ recipient: r, recipientFee: rf, allowed: true });
233
+ }
234
+ // Blocked recipients short-circuit
235
+ const blocked = feeRows.filter(f => !f.allowed).map(f => f.recipient);
236
+ if (blocked.length) {
237
+ return res.status(403).json({
238
+ status: 'error',
239
+ code: 'ERR_DELIVERY_BLOCKED',
240
+ description: `Blocked recipients: ${blocked.join(', ')}`,
241
+ blockedRecipients: blocked
242
+ });
243
+ }
244
+ const anyRecipientFee = feeRows.some(f => f.recipientFee > 0);
245
+ const requiresPayment = (deliveryFeeOnce > 0) || anyRecipientFee;
246
+ // ---------- Payment internalization (batch) ----------
247
+ const perRecipientOutputs = new Map();
248
+ if (requiresPayment) {
249
+ if (!payment?.tx || !Array.isArray(payment.outputs)) {
250
+ return res.status(400).json({
251
+ status: 'error',
252
+ code: 'ERR_MISSING_PAYMENT_TX',
253
+ description: 'Payment transaction data is required for payable delivery.'
254
+ });
255
+ }
256
+ // Enforce: index 0 is server delivery output (when needed)
257
+ if (deliveryFeeOnce > 0) {
258
+ if (payment.outputs.length === 0) {
259
+ return res.status(400).json({
260
+ status: 'error',
261
+ code: 'ERR_MISSING_DELIVERY_OUTPUT',
262
+ description: 'Delivery fee required but no outputs were provided.'
263
+ });
264
+ }
265
+ const serverDeliveryOutput = payment.outputs[0];
266
+ try {
267
+ const { getWallet } = await import('../app.js');
268
+ const wallet = await getWallet();
269
+ const internalizeResult = await wallet.internalizeAction({
270
+ tx: payment.tx,
271
+ outputs: [serverDeliveryOutput],
272
+ description: payment.description ?? 'MessageBox delivery payment (batch)'
273
+ });
274
+ if (!internalizeResult.accepted) {
275
+ return res.status(400).json({
276
+ status: 'error',
277
+ code: 'ERR_INSUFFICIENT_PAYMENT',
278
+ description: 'Payment was not accepted by the server.'
279
+ });
280
+ }
281
+ Logger.log('[DEBUG] Internalized server delivery output at index 0');
282
+ }
283
+ catch (error) {
284
+ Logger.error('[ERROR] Failed to internalize delivery fee payment:', error);
285
+ return res.status(500).json({
286
+ status: 'error',
287
+ code: 'ERR_INTERNALIZE_FAILED',
288
+ description: `Failed to internalize payment: ${error instanceof Error ? error.message : 'Unknown error'}`
289
+ });
290
+ }
291
+ }
292
+ // ---------- Build per-recipient outputs ----------
293
+ const recipientSideOutputs = payment.outputs.slice(deliveryFeeOnce > 0 ? 1 : 0);
294
+ console.log('Recipient side outputs:', recipientSideOutputs);
295
+ console.log('All outputs: ', payment.outputs);
296
+ const feeRecipients = feeRows.filter(f => f.recipientFee > 0).map(f => f.recipient);
297
+ // Try explicit mapping via customInstructions (in insertionRemittance OR paymentRemittance)
298
+ const outputsByRecipientKey = new Map();
299
+ const usedIndexes = new Set();
300
+ for (const out of recipientSideOutputs) {
301
+ const raw = out?.insertionRemittance?.customInstructions ??
302
+ out?.paymentRemittance?.customInstructions ??
303
+ out?.customInstructions;
304
+ if (!raw)
305
+ continue;
306
+ try {
307
+ const instr = typeof raw === 'string' ? JSON.parse(raw) : raw;
308
+ const key = instr?.recipientIdentityKey;
309
+ if (typeof key === 'string' && key.trim() !== '') {
310
+ if (!outputsByRecipientKey.has(key))
311
+ outputsByRecipientKey.set(key, []);
312
+ outputsByRecipientKey.get(key).push(out);
313
+ if (typeof out?.outputIndex === 'number') {
314
+ usedIndexes.add(out.outputIndex);
315
+ }
316
+ }
317
+ }
318
+ catch {
319
+ // ignore unparsable instructions
320
+ }
321
+ }
322
+ if (outputsByRecipientKey.size === 0) {
323
+ // No explicit tags: fallback to positional mapping for recipients that require a fee
324
+ if (recipientSideOutputs.length < feeRecipients.length) {
325
+ return res.status(400).json({
326
+ status: 'error',
327
+ code: 'ERR_INSUFFICIENT_OUTPUTS',
328
+ description: `Expected at least ${feeRecipients.length} recipient output(s) but received ${recipientSideOutputs.length}`
329
+ });
330
+ }
331
+ feeRecipients.forEach((r, idx) => {
332
+ const out = recipientSideOutputs[idx];
333
+ if (!perRecipientOutputs.has(r))
334
+ perRecipientOutputs.set(r, []);
335
+ perRecipientOutputs.get(r).push(out);
336
+ });
337
+ }
338
+ else {
339
+ // Use tagged outputs where present
340
+ for (const r of feeRecipients) {
341
+ const tagged = outputsByRecipientKey.get(r) ?? [];
342
+ if (tagged.length > 0) {
343
+ perRecipientOutputs.set(r, tagged);
344
+ }
345
+ }
346
+ // For any remaining fee recipients without tags, allocate unused outputs (positional)
347
+ const unmapped = feeRecipients.filter(r => !perRecipientOutputs.has(r));
348
+ if (unmapped.length > 0) {
349
+ const remaining = recipientSideOutputs.filter(o => {
350
+ const idx = o?.outputIndex;
351
+ return typeof idx === 'number' ? !usedIndexes.has(idx) : true;
352
+ });
353
+ if (remaining.length < unmapped.length) {
354
+ return res.status(400).json({
355
+ status: 'error',
356
+ code: 'ERR_INSUFFICIENT_OUTPUTS',
357
+ description: `Expected at least ${unmapped.length} additional recipient output(s) but only ${remaining.length} remain`
358
+ });
359
+ }
360
+ unmapped.forEach((r, i) => {
361
+ const out = remaining[i];
362
+ if (!perRecipientOutputs.has(r))
363
+ perRecipientOutputs.set(r, []);
364
+ perRecipientOutputs.get(r).push(out);
365
+ });
366
+ }
367
+ // Final safety check
368
+ for (const r of feeRecipients) {
369
+ if (!perRecipientOutputs.has(r) || perRecipientOutputs.get(r).length === 0) {
370
+ return res.status(400).json({
371
+ status: 'error',
372
+ code: 'ERR_MISSING_RECIPIENT_OUTPUTS',
373
+ description: `Recipient fee required but no outputs were provided for ${r}`
374
+ });
375
+ }
376
+ }
377
+ }
378
+ }
379
+ // ---------- Store messages (one per recipient) ----------
380
+ const results = [];
381
+ for (const { recipient: r } of feeRows) {
382
+ const mb = await knex('messageBox')
383
+ .where({ identityKey: r, type: boxType })
384
+ .select('messageBoxId')
385
+ .first();
386
+ const perRecipientMessageId = msgIdByRecipient.get(r);
387
+ if (!perRecipientMessageId) {
388
+ return res.status(400).json({
389
+ status: 'error',
390
+ code: 'ERR_INVALID_MESSAGEID',
391
+ description: `Missing messageId for recipient ${r}`
392
+ });
393
+ }
394
+ const perRecipientPayment = perRecipientOutputs.has(r) && req.body.payment
395
+ ? { ...req.body.payment, outputs: perRecipientOutputs.get(r) }
396
+ : undefined;
397
+ const storedBody = {
398
+ message: message.body,
399
+ ...(perRecipientPayment && { payment: perRecipientPayment })
400
+ };
401
+ try {
402
+ await knex('messages')
403
+ .insert({
404
+ messageId: perRecipientMessageId,
405
+ messageBoxId: mb?.messageBoxId ?? null,
406
+ sender: senderKey,
407
+ recipient: r,
408
+ body: JSON.stringify(storedBody),
409
+ created_at: new Date(),
410
+ updated_at: new Date()
411
+ })
412
+ .onConflict('messageId')
413
+ .ignore();
414
+ results.push({ recipient: r, messageId: perRecipientMessageId });
415
+ }
416
+ catch (error) {
417
+ if (error?.code === 'ER_DUP_ENTRY') {
418
+ return res.status(400).json({
419
+ status: 'error',
420
+ code: 'ERR_DUPLICATE_MESSAGE',
421
+ description: 'Duplicate message.'
422
+ });
423
+ }
424
+ throw error;
425
+ }
426
+ try {
427
+ if (shouldUseFCMDelivery(boxType)) {
428
+ await sendFCMNotification(r, { title: 'New Message', messageId: perRecipientMessageId });
429
+ }
430
+ }
431
+ catch (deliveryError) {
432
+ Logger.error('[ERROR] Error processing FCM delivery:', deliveryError);
433
+ }
434
+ }
435
+ return res.status(200).json({
436
+ status: 'success',
437
+ message: `Your message has been sent to ${results.length} recipient(s).`,
438
+ results
439
+ });
440
+ }
441
+ catch (error) {
442
+ Logger.error('[ERROR] Internal Server Error:', error);
443
+ return res.status(500).json({
444
+ status: 'error',
445
+ code: 'ERR_INTERNAL',
446
+ description: 'An internal error has occurred.'
447
+ });
448
+ }
449
+ }
450
+ };
451
+ //# sourceMappingURL=sendMessage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sendMessage.js","sourceRoot":"","sources":["../../../src/routes/sendMessage.ts"],"names":[],"mappings":"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"}