@bsv/message-box-client 1.1.9 → 1.1.11
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 +5 -5
- package/dist/cjs/src/MessageBoxClient.js +707 -87
- package/dist/cjs/src/MessageBoxClient.js.map +1 -1
- package/dist/cjs/src/PeerPayClient.js +61 -28
- package/dist/cjs/src/PeerPayClient.js.map +1 -1
- package/dist/cjs/src/Utils/logger.js +22 -21
- package/dist/cjs/src/Utils/logger.js.map +1 -1
- package/dist/cjs/src/types/permissions.js +6 -0
- package/dist/cjs/src/types/permissions.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/MessageBoxClient.js +593 -12
- package/dist/esm/src/MessageBoxClient.js.map +1 -1
- package/dist/esm/src/PeerPayClient.js +1 -1
- package/dist/esm/src/PeerPayClient.js.map +1 -1
- package/dist/esm/src/Utils/logger.js +17 -19
- package/dist/esm/src/Utils/logger.js.map +1 -1
- package/dist/esm/src/types/permissions.js +5 -0
- package/dist/esm/src/types/permissions.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/MessageBoxClient.d.ts +218 -13
- package/dist/types/src/MessageBoxClient.d.ts.map +1 -1
- package/dist/types/src/PeerPayClient.d.ts.map +1 -1
- package/dist/types/src/Utils/logger.d.ts +5 -8
- package/dist/types/src/Utils/logger.d.ts.map +1 -1
- package/dist/types/src/types/permissions.d.ts +75 -0
- package/dist/types/src/types/permissions.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +71 -2
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/package.json +5 -5
- package/src/MessageBoxClient.ts +732 -24
- package/src/PeerPayClient.ts +11 -11
- package/src/Utils/logger.ts +17 -19
- package/src/types/permissions.ts +81 -0
- package/src/types.ts +77 -2
|
@@ -22,11 +22,44 @@
|
|
|
22
22
|
* @author Project Babbage
|
|
23
23
|
* @license Open BSV License
|
|
24
24
|
*/
|
|
25
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
28
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
29
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
30
|
+
}
|
|
31
|
+
Object.defineProperty(o, k2, desc);
|
|
32
|
+
}) : (function(o, m, k, k2) {
|
|
33
|
+
if (k2 === undefined) k2 = k;
|
|
34
|
+
o[k2] = m[k];
|
|
35
|
+
}));
|
|
36
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
37
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
38
|
+
}) : function(o, v) {
|
|
39
|
+
o["default"] = v;
|
|
40
|
+
});
|
|
41
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
42
|
+
var ownKeys = function(o) {
|
|
43
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
44
|
+
var ar = [];
|
|
45
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
46
|
+
return ar;
|
|
47
|
+
};
|
|
48
|
+
return ownKeys(o);
|
|
49
|
+
};
|
|
50
|
+
return function (mod) {
|
|
51
|
+
if (mod && mod.__esModule) return mod;
|
|
52
|
+
var result = {};
|
|
53
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
54
|
+
__setModuleDefault(result, mod);
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
})();
|
|
25
58
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
59
|
exports.MessageBoxClient = void 0;
|
|
27
60
|
const sdk_1 = require("@bsv/sdk");
|
|
28
61
|
const authsocket_client_1 = require("@bsv/authsocket-client");
|
|
29
|
-
const
|
|
62
|
+
const Logger = __importStar(require("./Utils/logger.js"));
|
|
30
63
|
const DEFAULT_MAINNET_HOST = 'https://messagebox.babbage.systems';
|
|
31
64
|
const DEFAULT_TESTNET_HOST = 'https://staging-messagebox.babbage.systems';
|
|
32
65
|
/**
|
|
@@ -61,7 +94,7 @@ class MessageBoxClient {
|
|
|
61
94
|
* @constructor
|
|
62
95
|
* @param {Object} options - Initialization options for the MessageBoxClient.
|
|
63
96
|
* @param {string} [options.host] - The base URL of the MessageBox server. If omitted, defaults to mainnet/testnet hosts.
|
|
64
|
-
* @param {
|
|
97
|
+
* @param {WalletInterface} options.walletClient - Wallet instance used for authentication, signing, and encryption.
|
|
65
98
|
* @param {boolean} [options.enableLogging=false] - Whether to enable detailed debug logging to the console.
|
|
66
99
|
* @param {'local' | 'mainnet' | 'testnet'} [options.networkPreset='mainnet'] - Overlay network preset used for routing and advertisement lookup.
|
|
67
100
|
*
|
|
@@ -97,7 +130,7 @@ class MessageBoxClient {
|
|
|
97
130
|
networkPreset
|
|
98
131
|
});
|
|
99
132
|
if (enableLogging) {
|
|
100
|
-
|
|
133
|
+
Logger.enable();
|
|
101
134
|
}
|
|
102
135
|
}
|
|
103
136
|
/**
|
|
@@ -141,7 +174,7 @@ class MessageBoxClient {
|
|
|
141
174
|
const [firstAdvertisement] = await this.queryAdvertisements(identityKey, normalizedHost);
|
|
142
175
|
// 3. If none our found, anoint this host
|
|
143
176
|
if (firstAdvertisement == null || ((_a = firstAdvertisement === null || firstAdvertisement === void 0 ? void 0 : firstAdvertisement.host) === null || _a === void 0 ? void 0 : _a.trim()) === '' || (firstAdvertisement === null || firstAdvertisement === void 0 ? void 0 : firstAdvertisement.host) !== normalizedHost) {
|
|
144
|
-
|
|
177
|
+
Logger.log('[MB CLIENT] Anointing host:', normalizedHost);
|
|
145
178
|
const { txid } = await this.anointHost(normalizedHost);
|
|
146
179
|
if (txid == null || txid.trim() === '') {
|
|
147
180
|
throw new Error('Failed to anoint host: No transaction ID returned');
|
|
@@ -186,15 +219,15 @@ class MessageBoxClient {
|
|
|
186
219
|
if (this.myIdentityKey != null && this.myIdentityKey.trim() !== '') {
|
|
187
220
|
return this.myIdentityKey;
|
|
188
221
|
}
|
|
189
|
-
|
|
222
|
+
Logger.log('[MB CLIENT] Fetching identity key...');
|
|
190
223
|
try {
|
|
191
224
|
const keyResult = await this.walletClient.getPublicKey({ identityKey: true });
|
|
192
225
|
this.myIdentityKey = keyResult.publicKey;
|
|
193
|
-
|
|
226
|
+
Logger.log(`[MB CLIENT] Identity key fetched: ${this.myIdentityKey}`);
|
|
194
227
|
return this.myIdentityKey;
|
|
195
228
|
}
|
|
196
229
|
catch (error) {
|
|
197
|
-
|
|
230
|
+
Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
|
|
198
231
|
throw new Error('Identity key retrieval failed');
|
|
199
232
|
}
|
|
200
233
|
}
|
|
@@ -237,24 +270,24 @@ class MessageBoxClient {
|
|
|
237
270
|
*/
|
|
238
271
|
async initializeConnection() {
|
|
239
272
|
await this.assertInitialized();
|
|
240
|
-
|
|
273
|
+
Logger.log('[MB CLIENT] initializeConnection() STARTED');
|
|
241
274
|
if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
|
|
242
|
-
|
|
275
|
+
Logger.log('[MB CLIENT] Fetching identity key...');
|
|
243
276
|
try {
|
|
244
277
|
const keyResult = await this.walletClient.getPublicKey({ identityKey: true });
|
|
245
278
|
this.myIdentityKey = keyResult.publicKey;
|
|
246
|
-
|
|
279
|
+
Logger.log(`[MB CLIENT] Identity key fetched successfully: ${this.myIdentityKey}`);
|
|
247
280
|
}
|
|
248
281
|
catch (error) {
|
|
249
|
-
|
|
282
|
+
Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
|
|
250
283
|
throw new Error('Identity key retrieval failed');
|
|
251
284
|
}
|
|
252
285
|
}
|
|
253
286
|
if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
|
|
254
|
-
|
|
287
|
+
Logger.error('[MB CLIENT ERROR] Identity key is still missing after retrieval!');
|
|
255
288
|
throw new Error('Identity key is missing');
|
|
256
289
|
}
|
|
257
|
-
|
|
290
|
+
Logger.log('[MB CLIENT] Setting up WebSocket connection...');
|
|
258
291
|
if (this.socket == null) {
|
|
259
292
|
if (typeof this.host !== 'string' || this.host.trim() === '') {
|
|
260
293
|
throw new Error('Cannot initialize WebSocket: Host is not set');
|
|
@@ -264,11 +297,11 @@ class MessageBoxClient {
|
|
|
264
297
|
let authenticated = false;
|
|
265
298
|
this.socket.on('connect', () => {
|
|
266
299
|
var _a;
|
|
267
|
-
|
|
300
|
+
Logger.log('[MB CLIENT] Connected to WebSocket.');
|
|
268
301
|
if (!identitySent) {
|
|
269
|
-
|
|
302
|
+
Logger.log('[MB CLIENT] Sending authentication data:', this.myIdentityKey);
|
|
270
303
|
if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
|
|
271
|
-
|
|
304
|
+
Logger.error('[MB CLIENT ERROR] Cannot send authentication: Identity key is missing!');
|
|
272
305
|
}
|
|
273
306
|
else {
|
|
274
307
|
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.emit('authenticated', { identityKey: this.myIdentityKey });
|
|
@@ -278,28 +311,28 @@ class MessageBoxClient {
|
|
|
278
311
|
});
|
|
279
312
|
// Listen for authentication success from the server
|
|
280
313
|
this.socket.on('authenticationSuccess', (data) => {
|
|
281
|
-
|
|
314
|
+
Logger.log(`[MB CLIENT] WebSocket authentication successful: ${JSON.stringify(data)}`);
|
|
282
315
|
authenticated = true;
|
|
283
316
|
});
|
|
284
317
|
// Handle authentication failures
|
|
285
318
|
this.socket.on('authenticationFailed', (data) => {
|
|
286
|
-
|
|
319
|
+
Logger.error(`[MB CLIENT ERROR] WebSocket authentication failed: ${JSON.stringify(data)}`);
|
|
287
320
|
authenticated = false;
|
|
288
321
|
});
|
|
289
322
|
this.socket.on('disconnect', () => {
|
|
290
|
-
|
|
323
|
+
Logger.log('[MB CLIENT] Disconnected from MessageBox server');
|
|
291
324
|
this.socket = undefined;
|
|
292
325
|
identitySent = false;
|
|
293
326
|
authenticated = false;
|
|
294
327
|
});
|
|
295
328
|
this.socket.on('error', (error) => {
|
|
296
|
-
|
|
329
|
+
Logger.error('[MB CLIENT ERROR] WebSocket error:', error);
|
|
297
330
|
});
|
|
298
331
|
// Wait for authentication confirmation before proceeding
|
|
299
332
|
await new Promise((resolve, reject) => {
|
|
300
333
|
setTimeout(() => {
|
|
301
334
|
if (authenticated) {
|
|
302
|
-
|
|
335
|
+
Logger.log('[MB CLIENT] WebSocket fully authenticated and ready!');
|
|
303
336
|
resolve();
|
|
304
337
|
}
|
|
305
338
|
else {
|
|
@@ -330,7 +363,7 @@ class MessageBoxClient {
|
|
|
330
363
|
async resolveHostForRecipient(identityKey) {
|
|
331
364
|
const advertisementTokens = await this.queryAdvertisements(identityKey);
|
|
332
365
|
if (advertisementTokens.length === 0) {
|
|
333
|
-
|
|
366
|
+
Logger.warn(`[MB CLIENT] No advertisements for ${identityKey}, using default host ${this.host}`);
|
|
334
367
|
return this.host;
|
|
335
368
|
}
|
|
336
369
|
// Return the first host found
|
|
@@ -355,7 +388,7 @@ class MessageBoxClient {
|
|
|
355
388
|
query
|
|
356
389
|
});
|
|
357
390
|
if (result.type !== 'output-list') {
|
|
358
|
-
throw new Error(`Unexpected result type: ${result.type}`);
|
|
391
|
+
throw new Error(`Unexpected result type: ${String(result.type)}`);
|
|
359
392
|
}
|
|
360
393
|
for (const output of result.outputs) {
|
|
361
394
|
try {
|
|
@@ -380,7 +413,7 @@ class MessageBoxClient {
|
|
|
380
413
|
}
|
|
381
414
|
}
|
|
382
415
|
catch (err) {
|
|
383
|
-
|
|
416
|
+
Logger.error('[MB CLIENT ERROR] _queryAdvertisements failed:', err);
|
|
384
417
|
}
|
|
385
418
|
return hosts;
|
|
386
419
|
}
|
|
@@ -406,10 +439,10 @@ class MessageBoxClient {
|
|
|
406
439
|
async joinRoom(messageBox) {
|
|
407
440
|
var _a, _b;
|
|
408
441
|
await this.assertInitialized();
|
|
409
|
-
|
|
442
|
+
Logger.log(`[MB CLIENT] Attempting to join WebSocket room: ${messageBox}`);
|
|
410
443
|
// Ensure WebSocket connection is established first
|
|
411
444
|
if (this.socket == null) {
|
|
412
|
-
|
|
445
|
+
Logger.log('[MB CLIENT] No WebSocket connection. Initializing...');
|
|
413
446
|
await this.initializeConnection();
|
|
414
447
|
}
|
|
415
448
|
if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
|
|
@@ -417,17 +450,17 @@ class MessageBoxClient {
|
|
|
417
450
|
}
|
|
418
451
|
const roomId = `${(_a = this.myIdentityKey) !== null && _a !== void 0 ? _a : ''}-${messageBox}`;
|
|
419
452
|
if (this.joinedRooms.has(roomId)) {
|
|
420
|
-
|
|
453
|
+
Logger.log(`[MB CLIENT] Already joined WebSocket room: ${roomId}`);
|
|
421
454
|
return;
|
|
422
455
|
}
|
|
423
456
|
try {
|
|
424
|
-
|
|
457
|
+
Logger.log(`[MB CLIENT] Joining WebSocket room: ${roomId}`);
|
|
425
458
|
await ((_b = this.socket) === null || _b === void 0 ? void 0 : _b.emit('joinRoom', roomId));
|
|
426
459
|
this.joinedRooms.add(roomId);
|
|
427
|
-
|
|
460
|
+
Logger.log(`[MB CLIENT] Successfully joined room: ${roomId}`);
|
|
428
461
|
}
|
|
429
462
|
catch (error) {
|
|
430
|
-
|
|
463
|
+
Logger.error(`[MB CLIENT ERROR] Failed to join WebSocket room: ${roomId}`, error);
|
|
431
464
|
}
|
|
432
465
|
}
|
|
433
466
|
/**
|
|
@@ -461,7 +494,7 @@ class MessageBoxClient {
|
|
|
461
494
|
async listenForLiveMessages({ onMessage, messageBox }) {
|
|
462
495
|
var _a;
|
|
463
496
|
await this.assertInitialized();
|
|
464
|
-
|
|
497
|
+
Logger.log(`[MB CLIENT] Setting up listener for WebSocket room: ${messageBox}`);
|
|
465
498
|
// Ensure WebSocket connection and room join
|
|
466
499
|
await this.joinRoom(messageBox);
|
|
467
500
|
// Ensure identity key is available before creating roomId
|
|
@@ -469,10 +502,10 @@ class MessageBoxClient {
|
|
|
469
502
|
throw new Error('[MB CLIENT ERROR] Identity key is missing. Cannot construct room ID.');
|
|
470
503
|
}
|
|
471
504
|
const roomId = `${this.myIdentityKey}-${messageBox}`;
|
|
472
|
-
|
|
505
|
+
Logger.log(`[MB CLIENT] Listening for messages in room: ${roomId}`);
|
|
473
506
|
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.on(`sendMessage-${roomId}`, (message) => {
|
|
474
507
|
void (async () => {
|
|
475
|
-
|
|
508
|
+
Logger.log(`[MB CLIENT] Received message in room ${roomId}:`, message);
|
|
476
509
|
try {
|
|
477
510
|
let parsedBody = message.body;
|
|
478
511
|
if (typeof parsedBody === 'string') {
|
|
@@ -486,7 +519,7 @@ class MessageBoxClient {
|
|
|
486
519
|
if (parsedBody != null &&
|
|
487
520
|
typeof parsedBody === 'object' &&
|
|
488
521
|
typeof parsedBody.encryptedMessage === 'string') {
|
|
489
|
-
|
|
522
|
+
Logger.log(`[MB CLIENT] Decrypting message from ${String(message.sender)}...`);
|
|
490
523
|
const decrypted = await this.walletClient.decrypt({
|
|
491
524
|
protocolID: [1, 'messagebox'],
|
|
492
525
|
keyID: '1',
|
|
@@ -496,7 +529,7 @@ class MessageBoxClient {
|
|
|
496
529
|
message.body = sdk_1.Utils.toUTF8(decrypted.plaintext);
|
|
497
530
|
}
|
|
498
531
|
else {
|
|
499
|
-
|
|
532
|
+
Logger.log('[MB CLIENT] Message is not encrypted.');
|
|
500
533
|
message.body = typeof parsedBody === 'string'
|
|
501
534
|
? parsedBody
|
|
502
535
|
: (() => { try {
|
|
@@ -508,7 +541,7 @@ class MessageBoxClient {
|
|
|
508
541
|
}
|
|
509
542
|
}
|
|
510
543
|
catch (err) {
|
|
511
|
-
|
|
544
|
+
Logger.error('[MB CLIENT ERROR] Failed to parse or decrypt live message:', err);
|
|
512
545
|
message.body = '[Error: Failed to decrypt or parse message]';
|
|
513
546
|
}
|
|
514
547
|
onMessage(message);
|
|
@@ -544,7 +577,7 @@ class MessageBoxClient {
|
|
|
544
577
|
* body: { amount: 1000 }
|
|
545
578
|
* })
|
|
546
579
|
*/
|
|
547
|
-
async sendLiveMessage({ recipient, messageBox, body, messageId, skipEncryption }) {
|
|
580
|
+
async sendLiveMessage({ recipient, messageBox, body, messageId, skipEncryption, checkPermissions }) {
|
|
548
581
|
await this.assertInitialized();
|
|
549
582
|
if (recipient == null || recipient.trim() === '') {
|
|
550
583
|
throw new Error('[MB CLIENT ERROR] Recipient identity key is required');
|
|
@@ -559,7 +592,7 @@ class MessageBoxClient {
|
|
|
559
592
|
await this.joinRoom(messageBox);
|
|
560
593
|
// Fallback to HTTP if WebSocket is not connected
|
|
561
594
|
if (this.socket == null || !this.socket.connected) {
|
|
562
|
-
|
|
595
|
+
Logger.warn('[MB CLIENT WARNING] WebSocket not connected, falling back to HTTP');
|
|
563
596
|
const targetHost = await this.resolveHostForRecipient(recipient);
|
|
564
597
|
return await this.sendMessage({ recipient, messageBox, body }, targetHost);
|
|
565
598
|
}
|
|
@@ -574,11 +607,11 @@ class MessageBoxClient {
|
|
|
574
607
|
finalMessageId = messageId !== null && messageId !== void 0 ? messageId : Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
575
608
|
}
|
|
576
609
|
catch (error) {
|
|
577
|
-
|
|
610
|
+
Logger.error('[MB CLIENT ERROR] Failed to generate HMAC:', error);
|
|
578
611
|
throw new Error('Failed to generate message identifier.');
|
|
579
612
|
}
|
|
580
613
|
const roomId = `${recipient}-${messageBox}`;
|
|
581
|
-
|
|
614
|
+
Logger.log(`[MB CLIENT] Sending WebSocket message to room: ${roomId}`);
|
|
582
615
|
let outgoingBody;
|
|
583
616
|
if (skipEncryption === true) {
|
|
584
617
|
outgoingBody = typeof body === 'string' ? body : JSON.stringify(body);
|
|
@@ -606,15 +639,16 @@ class MessageBoxClient {
|
|
|
606
639
|
if (typeof (socketAny === null || socketAny === void 0 ? void 0 : socketAny.off) === 'function') {
|
|
607
640
|
socketAny.off(ackEvent, ackHandler);
|
|
608
641
|
}
|
|
609
|
-
|
|
642
|
+
Logger.log('[MB CLIENT] Received WebSocket acknowledgment:', response);
|
|
610
643
|
if (response == null || response.status !== 'success') {
|
|
611
|
-
|
|
644
|
+
Logger.warn('[MB CLIENT] WebSocket message failed or returned unexpected response. Falling back to HTTP.');
|
|
612
645
|
const fallbackMessage = {
|
|
613
646
|
recipient,
|
|
614
647
|
messageBox,
|
|
615
648
|
body,
|
|
616
649
|
messageId: finalMessageId,
|
|
617
|
-
skipEncryption
|
|
650
|
+
skipEncryption,
|
|
651
|
+
checkPermissions
|
|
618
652
|
};
|
|
619
653
|
this.resolveHostForRecipient(recipient)
|
|
620
654
|
.then(async (host) => {
|
|
@@ -624,7 +658,7 @@ class MessageBoxClient {
|
|
|
624
658
|
.catch(reject);
|
|
625
659
|
}
|
|
626
660
|
else {
|
|
627
|
-
|
|
661
|
+
Logger.log('[MB CLIENT] Message sent successfully via WebSocket:', response);
|
|
628
662
|
resolve(response);
|
|
629
663
|
}
|
|
630
664
|
};
|
|
@@ -647,13 +681,14 @@ class MessageBoxClient {
|
|
|
647
681
|
if (typeof (socketAny === null || socketAny === void 0 ? void 0 : socketAny.off) === 'function') {
|
|
648
682
|
socketAny.off(ackEvent, ackHandler);
|
|
649
683
|
}
|
|
650
|
-
|
|
684
|
+
Logger.warn('[CLIENT] WebSocket acknowledgment timed out, falling back to HTTP');
|
|
651
685
|
const fallbackMessage = {
|
|
652
686
|
recipient,
|
|
653
687
|
messageBox,
|
|
654
688
|
body,
|
|
655
689
|
messageId: finalMessageId,
|
|
656
|
-
skipEncryption
|
|
690
|
+
skipEncryption,
|
|
691
|
+
checkPermissions
|
|
657
692
|
};
|
|
658
693
|
this.resolveHostForRecipient(recipient)
|
|
659
694
|
.then(async (host) => {
|
|
@@ -683,14 +718,14 @@ class MessageBoxClient {
|
|
|
683
718
|
async leaveRoom(messageBox) {
|
|
684
719
|
await this.assertInitialized();
|
|
685
720
|
if (this.socket == null) {
|
|
686
|
-
|
|
721
|
+
Logger.warn('[MB CLIENT] Attempted to leave a room but WebSocket is not connected.');
|
|
687
722
|
return;
|
|
688
723
|
}
|
|
689
724
|
if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
|
|
690
725
|
throw new Error('[MB CLIENT ERROR] Identity key is not defined');
|
|
691
726
|
}
|
|
692
727
|
const roomId = `${this.myIdentityKey}-${messageBox}`;
|
|
693
|
-
|
|
728
|
+
Logger.log(`[MB CLIENT] Leaving WebSocket room: ${roomId}`);
|
|
694
729
|
this.socket.emit('leaveRoom', roomId);
|
|
695
730
|
// Ensure the room is removed from tracking
|
|
696
731
|
this.joinedRooms.delete(roomId);
|
|
@@ -711,12 +746,12 @@ class MessageBoxClient {
|
|
|
711
746
|
async disconnectWebSocket() {
|
|
712
747
|
await this.assertInitialized();
|
|
713
748
|
if (this.socket != null) {
|
|
714
|
-
|
|
749
|
+
Logger.log('[MB CLIENT] Closing WebSocket connection...');
|
|
715
750
|
this.socket.disconnect();
|
|
716
751
|
this.socket = undefined;
|
|
717
752
|
}
|
|
718
753
|
else {
|
|
719
|
-
|
|
754
|
+
Logger.log('[MB CLIENT] No active WebSocket connection to close.');
|
|
720
755
|
}
|
|
721
756
|
}
|
|
722
757
|
/**
|
|
@@ -758,6 +793,33 @@ class MessageBoxClient {
|
|
|
758
793
|
if (message.body == null || (typeof message.body === 'string' && message.body.trim().length === 0)) {
|
|
759
794
|
throw new Error('Every message must have a body!');
|
|
760
795
|
}
|
|
796
|
+
// Optional permission checking for backwards compatibility
|
|
797
|
+
let paymentData;
|
|
798
|
+
if (message.checkPermissions === true) {
|
|
799
|
+
try {
|
|
800
|
+
Logger.log('[MB CLIENT] Checking permissions and fees for message...');
|
|
801
|
+
// Get quote to check if payment is required
|
|
802
|
+
const quote = await this.getMessageBoxQuote({
|
|
803
|
+
recipient: message.recipient,
|
|
804
|
+
messageBox: message.messageBox
|
|
805
|
+
});
|
|
806
|
+
if (quote.recipientFee === -1) {
|
|
807
|
+
throw new Error('You have been blocked from sending messages to this recipient.');
|
|
808
|
+
}
|
|
809
|
+
if (quote.recipientFee > 0 || quote.deliveryFee > 0) {
|
|
810
|
+
const requiredPayment = quote.recipientFee + quote.deliveryFee;
|
|
811
|
+
if (requiredPayment > 0) {
|
|
812
|
+
Logger.log(`[MB CLIENT] Creating payment of ${requiredPayment} sats for message...`);
|
|
813
|
+
// Create payment using helper method
|
|
814
|
+
paymentData = await this.createMessagePayment(message.recipient, quote, overrideHost);
|
|
815
|
+
Logger.log('[MB CLIENT] Payment data prepared:', paymentData);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
catch (error) {
|
|
820
|
+
throw new Error(`Permission check failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
761
823
|
let messageId;
|
|
762
824
|
try {
|
|
763
825
|
const hmac = await this.walletClient.createHmac({
|
|
@@ -769,7 +831,7 @@ class MessageBoxClient {
|
|
|
769
831
|
messageId = (_a = message.messageId) !== null && _a !== void 0 ? _a : Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
770
832
|
}
|
|
771
833
|
catch (error) {
|
|
772
|
-
|
|
834
|
+
Logger.error('[MB CLIENT ERROR] Failed to generate HMAC:', error);
|
|
773
835
|
throw new Error('Failed to generate message identifier.');
|
|
774
836
|
}
|
|
775
837
|
let finalBody;
|
|
@@ -790,20 +852,21 @@ class MessageBoxClient {
|
|
|
790
852
|
...message,
|
|
791
853
|
messageId,
|
|
792
854
|
body: finalBody
|
|
793
|
-
}
|
|
855
|
+
},
|
|
856
|
+
...(paymentData != null && { payment: paymentData })
|
|
794
857
|
};
|
|
795
858
|
try {
|
|
796
859
|
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : await this.resolveHostForRecipient(message.recipient);
|
|
797
|
-
|
|
798
|
-
|
|
860
|
+
Logger.log('[MB CLIENT] Sending HTTP request to:', `${finalHost}/sendMessage`);
|
|
861
|
+
Logger.log('[MB CLIENT] Request Body:', JSON.stringify(requestBody, null, 2));
|
|
799
862
|
if (this.myIdentityKey == null || this.myIdentityKey === '') {
|
|
800
863
|
try {
|
|
801
864
|
const keyResult = await this.walletClient.getPublicKey({ identityKey: true });
|
|
802
865
|
this.myIdentityKey = keyResult.publicKey;
|
|
803
|
-
|
|
866
|
+
Logger.log(`[MB CLIENT] Fetched identity key before sending request: ${this.myIdentityKey}`);
|
|
804
867
|
}
|
|
805
868
|
catch (error) {
|
|
806
|
-
|
|
869
|
+
Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
|
|
807
870
|
throw new Error('Identity key retrieval failed');
|
|
808
871
|
}
|
|
809
872
|
}
|
|
@@ -818,20 +881,20 @@ class MessageBoxClient {
|
|
|
818
881
|
throw new Error('[MB CLIENT ERROR] Response body has already been used!');
|
|
819
882
|
}
|
|
820
883
|
const parsedResponse = await response.json();
|
|
821
|
-
|
|
884
|
+
Logger.log('[MB CLIENT] Raw Response Body:', parsedResponse);
|
|
822
885
|
if (!response.ok) {
|
|
823
|
-
|
|
886
|
+
Logger.error(`[MB CLIENT ERROR] Failed to send message. HTTP ${response.status}: ${response.statusText}`);
|
|
824
887
|
throw new Error(`Message sending failed: HTTP ${response.status} - ${response.statusText}`);
|
|
825
888
|
}
|
|
826
889
|
if (parsedResponse.status !== 'success') {
|
|
827
|
-
|
|
890
|
+
Logger.error(`[MB CLIENT ERROR] Server returned an error: ${String(parsedResponse.description)}`);
|
|
828
891
|
throw new Error((_b = parsedResponse.description) !== null && _b !== void 0 ? _b : 'Unknown error from server.');
|
|
829
892
|
}
|
|
830
|
-
|
|
893
|
+
Logger.log('[MB CLIENT] Message successfully sent.');
|
|
831
894
|
return { ...parsedResponse, messageId };
|
|
832
895
|
}
|
|
833
896
|
catch (error) {
|
|
834
|
-
|
|
897
|
+
Logger.error('[MB CLIENT ERROR] Network or timeout error:', error);
|
|
835
898
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
836
899
|
throw new Error(`Failed to send message: ${errorMessage}`);
|
|
837
900
|
}
|
|
@@ -860,26 +923,26 @@ class MessageBoxClient {
|
|
|
860
923
|
* const { txid } = await client.anointHost('https://my-messagebox.io')
|
|
861
924
|
*/
|
|
862
925
|
async anointHost(host) {
|
|
863
|
-
|
|
926
|
+
Logger.log('[MB CLIENT] Starting anointHost...');
|
|
864
927
|
try {
|
|
865
928
|
if (!host.startsWith('http')) {
|
|
866
929
|
throw new Error('Invalid host URL');
|
|
867
930
|
}
|
|
868
931
|
const identityKey = await this.getIdentityKey();
|
|
869
|
-
|
|
932
|
+
Logger.log('[MB CLIENT] Fields - Identity:', identityKey, 'Host:', host);
|
|
870
933
|
const fields = [
|
|
871
934
|
sdk_1.Utils.toArray(identityKey, 'hex'),
|
|
872
935
|
sdk_1.Utils.toArray(host, 'utf8')
|
|
873
936
|
];
|
|
874
937
|
const pushdrop = new sdk_1.PushDrop(this.walletClient);
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
938
|
+
Logger.log('Fields:', fields.map(a => sdk_1.Utils.toHex(a)));
|
|
939
|
+
Logger.log('ProtocolID:', [1, 'messagebox advertisement']);
|
|
940
|
+
Logger.log('KeyID:', '1');
|
|
941
|
+
Logger.log('SignAs:', 'self');
|
|
942
|
+
Logger.log('anyoneCanSpend:', false);
|
|
943
|
+
Logger.log('forSelf:', true);
|
|
881
944
|
const script = await pushdrop.lock(fields, [1, 'messagebox advertisement'], '1', 'anyone', true);
|
|
882
|
-
|
|
945
|
+
Logger.log('[MB CLIENT] PushDrop script:', script.toASM());
|
|
883
946
|
const { tx, txid } = await this.walletClient.createAction({
|
|
884
947
|
description: 'Anoint host for overlay routing',
|
|
885
948
|
outputs: [{
|
|
@@ -890,13 +953,13 @@ class MessageBoxClient {
|
|
|
890
953
|
}],
|
|
891
954
|
options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
|
|
892
955
|
});
|
|
893
|
-
|
|
956
|
+
Logger.log('[MB CLIENT] Transaction created:', txid);
|
|
894
957
|
if (tx !== undefined) {
|
|
895
958
|
const broadcaster = new sdk_1.TopicBroadcaster(['tm_messagebox'], {
|
|
896
959
|
networkPreset: this.networkPreset
|
|
897
960
|
});
|
|
898
961
|
const result = await broadcaster.broadcast(sdk_1.Transaction.fromAtomicBEEF(tx));
|
|
899
|
-
|
|
962
|
+
Logger.log('[MB CLIENT] Advertisement broadcast succeeded. TXID:', result.txid);
|
|
900
963
|
if (typeof result.txid !== 'string') {
|
|
901
964
|
throw new Error('Anoint failed: broadcast did not return a txid');
|
|
902
965
|
}
|
|
@@ -905,7 +968,7 @@ class MessageBoxClient {
|
|
|
905
968
|
throw new Error('Anoint failed: failed to create action!');
|
|
906
969
|
}
|
|
907
970
|
catch (err) {
|
|
908
|
-
|
|
971
|
+
Logger.error('[MB CLIENT ERROR] anointHost threw:', err);
|
|
909
972
|
throw err;
|
|
910
973
|
}
|
|
911
974
|
}
|
|
@@ -923,7 +986,7 @@ class MessageBoxClient {
|
|
|
923
986
|
* const { txid } = await client.revokeHost('https://my-messagebox.io')
|
|
924
987
|
*/
|
|
925
988
|
async revokeHostAdvertisement(advertisementToken) {
|
|
926
|
-
|
|
989
|
+
Logger.log('[MB CLIENT] Starting revokeHost...');
|
|
927
990
|
const outpoint = `${advertisementToken.txid}.${advertisementToken.outputIndex}`;
|
|
928
991
|
try {
|
|
929
992
|
const { signableTransaction } = await this.walletClient.createAction({
|
|
@@ -965,14 +1028,14 @@ class MessageBoxClient {
|
|
|
965
1028
|
networkPreset: this.networkPreset
|
|
966
1029
|
});
|
|
967
1030
|
const result = await broadcaster.broadcast(sdk_1.Transaction.fromAtomicBEEF(signedTx));
|
|
968
|
-
|
|
1031
|
+
Logger.log('[MB CLIENT] Revocation broadcast succeeded. TXID:', result.txid);
|
|
969
1032
|
if (typeof result.txid !== 'string') {
|
|
970
1033
|
throw new Error('Revoke failed: broadcast did not return a txid');
|
|
971
1034
|
}
|
|
972
1035
|
return { txid: result.txid };
|
|
973
1036
|
}
|
|
974
1037
|
catch (err) {
|
|
975
|
-
|
|
1038
|
+
Logger.error('[MB CLIENT ERROR] revokeHost threw:', err);
|
|
976
1039
|
throw err;
|
|
977
1040
|
}
|
|
978
1041
|
}
|
|
@@ -988,9 +1051,16 @@ class MessageBoxClient {
|
|
|
988
1051
|
*
|
|
989
1052
|
* Each message is:
|
|
990
1053
|
* - Parsed and, if encrypted, decrypted using AES-256-GCM via BRC-2-compliant ECDH key derivation and symmetric encryption.
|
|
1054
|
+
* - Automatically processed for payments: if the message includes recipient fee payments, they are internalized using `walletClient.internalizeAction()`.
|
|
991
1055
|
* - Returned as a normalized `PeerMessage` with readable string body content.
|
|
992
1056
|
*
|
|
993
|
-
*
|
|
1057
|
+
* Payment Processing:
|
|
1058
|
+
* - Detects messages that include payment data (from paid message delivery).
|
|
1059
|
+
* - Automatically internalizes recipient payment outputs, allowing you to receive payments without additional API calls.
|
|
1060
|
+
* - Only recipient payments are stored with messages - delivery fees are already processed by the server.
|
|
1061
|
+
* - Continues processing messages even if payment internalization fails.
|
|
1062
|
+
*
|
|
1063
|
+
* Decryption automatically derives a shared secret using the sender's identity key and the receiver's child private key.
|
|
994
1064
|
* If the sender is the same as the recipient, the `counterparty` is set to `'self'`.
|
|
995
1065
|
*
|
|
996
1066
|
* @throws {Error} If no messageBox is specified, the request fails, or the server returns an error.
|
|
@@ -998,8 +1068,10 @@ class MessageBoxClient {
|
|
|
998
1068
|
* @example
|
|
999
1069
|
* const messages = await client.listMessages({ messageBox: 'inbox' })
|
|
1000
1070
|
* messages.forEach(msg => console.log(msg.sender, msg.body))
|
|
1071
|
+
* // Payments included with messages are automatically received
|
|
1001
1072
|
*/
|
|
1002
1073
|
async listMessages({ messageBox, host }) {
|
|
1074
|
+
var _a, _b;
|
|
1003
1075
|
await this.assertInitialized();
|
|
1004
1076
|
if (messageBox.trim() === '') {
|
|
1005
1077
|
throw new Error('MessageBox cannot be empty');
|
|
@@ -1013,7 +1085,7 @@ class MessageBoxClient {
|
|
|
1013
1085
|
const fetchFromHost = async (host) => {
|
|
1014
1086
|
var _a;
|
|
1015
1087
|
try {
|
|
1016
|
-
|
|
1088
|
+
Logger.log(`[MB CLIENT] Listing messages from ${host}…`);
|
|
1017
1089
|
const res = await this.authFetch.fetch(`${host}/listMessages`, {
|
|
1018
1090
|
method: 'POST',
|
|
1019
1091
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1027,7 +1099,7 @@ class MessageBoxClient {
|
|
|
1027
1099
|
return data.messages;
|
|
1028
1100
|
}
|
|
1029
1101
|
catch (err) {
|
|
1030
|
-
|
|
1102
|
+
Logger.log(`[MB CLIENT DEBUG] listMessages failed for ${host}:`, err);
|
|
1031
1103
|
throw err; // re-throw to be caught in the settled promise
|
|
1032
1104
|
}
|
|
1033
1105
|
};
|
|
@@ -1070,25 +1142,67 @@ class MessageBoxClient {
|
|
|
1070
1142
|
for (const message of messages) {
|
|
1071
1143
|
try {
|
|
1072
1144
|
const parsedBody = typeof message.body === 'string' ? tryParse(message.body) : message.body;
|
|
1145
|
+
let messageContent = parsedBody;
|
|
1146
|
+
let paymentData;
|
|
1073
1147
|
if (parsedBody != null &&
|
|
1074
1148
|
typeof parsedBody === 'object' &&
|
|
1075
|
-
|
|
1076
|
-
|
|
1149
|
+
'message' in parsedBody) {
|
|
1150
|
+
messageContent = (_a = parsedBody.message) === null || _a === void 0 ? void 0 : _a.body;
|
|
1151
|
+
paymentData = parsedBody.payment;
|
|
1152
|
+
}
|
|
1153
|
+
// Process payment if present - server now only stores recipient payments
|
|
1154
|
+
if ((paymentData === null || paymentData === void 0 ? void 0 : paymentData.tx) != null && paymentData.outputs != null) {
|
|
1155
|
+
try {
|
|
1156
|
+
Logger.log(`[MB CLIENT] Processing recipient payment in message from ${String(message.sender)}…`);
|
|
1157
|
+
// All outputs in the stored payment data are for the recipient
|
|
1158
|
+
// (delivery fees are already processed by the server)
|
|
1159
|
+
const recipientOutputs = paymentData.outputs.filter(output => output.protocol === 'wallet payment');
|
|
1160
|
+
if (recipientOutputs.length > 0) {
|
|
1161
|
+
Logger.log(`[MB CLIENT] Internalizing ${recipientOutputs.length} recipient payment output(s)…`);
|
|
1162
|
+
const internalizeResult = await this.walletClient.internalizeAction({
|
|
1163
|
+
tx: paymentData.tx,
|
|
1164
|
+
outputs: recipientOutputs,
|
|
1165
|
+
description: (_b = paymentData.description) !== null && _b !== void 0 ? _b : 'MessageBox recipient payment'
|
|
1166
|
+
});
|
|
1167
|
+
if (internalizeResult.accepted) {
|
|
1168
|
+
Logger.log('[MB CLIENT] Successfully internalized recipient payment');
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
Logger.warn('[MB CLIENT] Recipient payment internalization was not accepted');
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
Logger.log('[MB CLIENT] No wallet payment outputs found in payment data');
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
catch (paymentError) {
|
|
1179
|
+
Logger.error('[MB CLIENT ERROR] Failed to internalize recipient payment:', paymentError);
|
|
1180
|
+
// Continue processing the message even if payment fails
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
// Handle message decryption
|
|
1184
|
+
if (messageContent != null &&
|
|
1185
|
+
typeof messageContent === 'object' &&
|
|
1186
|
+
typeof messageContent.encryptedMessage === 'string') {
|
|
1187
|
+
Logger.log(`[MB CLIENT] Decrypting message from ${String(message.sender)}…`);
|
|
1077
1188
|
const decrypted = await this.walletClient.decrypt({
|
|
1078
1189
|
protocolID: [1, 'messagebox'],
|
|
1079
1190
|
keyID: '1',
|
|
1080
1191
|
counterparty: message.sender,
|
|
1081
|
-
ciphertext: sdk_1.Utils.toArray(
|
|
1192
|
+
ciphertext: sdk_1.Utils.toArray(messageContent.encryptedMessage, 'base64')
|
|
1082
1193
|
});
|
|
1083
1194
|
const decryptedText = sdk_1.Utils.toUTF8(decrypted.plaintext);
|
|
1084
1195
|
message.body = tryParse(decryptedText);
|
|
1085
1196
|
}
|
|
1086
1197
|
else {
|
|
1087
|
-
message.body
|
|
1198
|
+
// Handle both old format (direct content) and new format (message.body)
|
|
1199
|
+
message.body = messageContent != null
|
|
1200
|
+
? (typeof messageContent === 'string' ? messageContent : messageContent)
|
|
1201
|
+
: parsedBody;
|
|
1088
1202
|
}
|
|
1089
1203
|
}
|
|
1090
1204
|
catch (err) {
|
|
1091
|
-
|
|
1205
|
+
Logger.error('[MB CLIENT ERROR] Failed to parse or decrypt message in list:', err);
|
|
1092
1206
|
message.body = '[Error: Failed to decrypt or parse message]';
|
|
1093
1207
|
}
|
|
1094
1208
|
}
|
|
@@ -1123,7 +1237,7 @@ class MessageBoxClient {
|
|
|
1123
1237
|
if (!Array.isArray(messageIds) || messageIds.length === 0) {
|
|
1124
1238
|
throw new Error('Message IDs array cannot be empty');
|
|
1125
1239
|
}
|
|
1126
|
-
|
|
1240
|
+
Logger.log(`[MB CLIENT] Acknowledging messages ${JSON.stringify(messageIds)}…`);
|
|
1127
1241
|
let hosts = host != null ? [host] : [];
|
|
1128
1242
|
if (hosts.length === 0) {
|
|
1129
1243
|
// 1. Determine all hosts (advertised + default)
|
|
@@ -1144,11 +1258,11 @@ class MessageBoxClient {
|
|
|
1144
1258
|
const data = await res.json();
|
|
1145
1259
|
if (data.status === 'error')
|
|
1146
1260
|
throw new Error(data.description);
|
|
1147
|
-
|
|
1261
|
+
Logger.log(`[MB CLIENT] Acknowledged on ${host}`);
|
|
1148
1262
|
return data.status;
|
|
1149
1263
|
}
|
|
1150
1264
|
catch (err) {
|
|
1151
|
-
|
|
1265
|
+
Logger.warn(`[MB CLIENT WARN] acknowledgeMessage failed for ${host}:`, err);
|
|
1152
1266
|
return null;
|
|
1153
1267
|
}
|
|
1154
1268
|
};
|
|
@@ -1166,6 +1280,512 @@ class MessageBoxClient {
|
|
|
1166
1280
|
}
|
|
1167
1281
|
throw new Error(`Failed to acknowledge messages on all hosts: ${errs.map(e => String(e)).join('; ')}`);
|
|
1168
1282
|
}
|
|
1283
|
+
// ===========================
|
|
1284
|
+
// PERMISSION MANAGEMENT METHODS
|
|
1285
|
+
// ===========================
|
|
1286
|
+
/**
|
|
1287
|
+
* @method setMessageBoxPermission
|
|
1288
|
+
* @async
|
|
1289
|
+
* @param {SetMessageBoxPermissionParams} params - Permission configuration
|
|
1290
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1291
|
+
* @returns {Promise<void>} Permission status after setting
|
|
1292
|
+
*
|
|
1293
|
+
* @description
|
|
1294
|
+
* Sets permission for receiving messages in a specific messageBox.
|
|
1295
|
+
* Can set sender-specific permissions or box-wide defaults.
|
|
1296
|
+
*
|
|
1297
|
+
* @example
|
|
1298
|
+
* // Set box-wide default: allow notifications for 10 sats
|
|
1299
|
+
* await client.setMessageBoxPermission({ messageBox: 'notifications', recipientFee: 10 })
|
|
1300
|
+
*
|
|
1301
|
+
* // Block specific sender
|
|
1302
|
+
* await client.setMessageBoxPermission({
|
|
1303
|
+
* messageBox: 'notifications',
|
|
1304
|
+
* sender: '03abc123...',
|
|
1305
|
+
* recipientFee: -1
|
|
1306
|
+
* })
|
|
1307
|
+
*/
|
|
1308
|
+
async setMessageBoxPermission(params, overrideHost) {
|
|
1309
|
+
await this.assertInitialized();
|
|
1310
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
|
|
1311
|
+
Logger.log('[MB CLIENT] Setting messageBox permission...');
|
|
1312
|
+
const response = await this.authFetch.fetch(`${finalHost}/permissions/set`, {
|
|
1313
|
+
method: 'POST',
|
|
1314
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1315
|
+
body: JSON.stringify({
|
|
1316
|
+
messageBox: params.messageBox,
|
|
1317
|
+
recipientFee: params.recipientFee,
|
|
1318
|
+
...(params.sender != null && { sender: params.sender })
|
|
1319
|
+
})
|
|
1320
|
+
});
|
|
1321
|
+
if (!response.ok) {
|
|
1322
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1323
|
+
throw new Error(`Failed to set permission: HTTP ${response.status} - ${String(errorData.description) !== '' ? String(errorData.description) : response.statusText}`);
|
|
1324
|
+
}
|
|
1325
|
+
const { status, description } = await response.json();
|
|
1326
|
+
if (status === 'error') {
|
|
1327
|
+
throw new Error(description !== null && description !== void 0 ? description : 'Failed to set permission');
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* @method getMessageBoxPermission
|
|
1332
|
+
* @async
|
|
1333
|
+
* @param {GetMessageBoxPermissionParams} params - Permission query parameters
|
|
1334
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1335
|
+
* @returns {Promise<MessageBoxPermission | null>} Permission data (null if not set)
|
|
1336
|
+
*
|
|
1337
|
+
* @description
|
|
1338
|
+
* Gets current permission data for a sender/messageBox combination.
|
|
1339
|
+
* Returns null if no permission is set.
|
|
1340
|
+
*
|
|
1341
|
+
* @example
|
|
1342
|
+
* const status = await client.getMessageBoxPermission({
|
|
1343
|
+
* recipient: '03def456...',
|
|
1344
|
+
* messageBox: 'notifications',
|
|
1345
|
+
* sender: '03abc123...'
|
|
1346
|
+
* })
|
|
1347
|
+
*/
|
|
1348
|
+
async getMessageBoxPermission(params, overrideHost) {
|
|
1349
|
+
var _a;
|
|
1350
|
+
await this.assertInitialized();
|
|
1351
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : await this.resolveHostForRecipient(params.recipient);
|
|
1352
|
+
const queryParams = new URLSearchParams({
|
|
1353
|
+
recipient: params.recipient,
|
|
1354
|
+
messageBox: params.messageBox,
|
|
1355
|
+
...(params.sender != null && { sender: params.sender })
|
|
1356
|
+
});
|
|
1357
|
+
Logger.log('[MB CLIENT] Getting messageBox permission...');
|
|
1358
|
+
const response = await this.authFetch.fetch(`${finalHost}/permissions/get?${queryParams.toString()}`, {
|
|
1359
|
+
method: 'GET'
|
|
1360
|
+
});
|
|
1361
|
+
if (!response.ok) {
|
|
1362
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1363
|
+
throw new Error(`Failed to get permission: HTTP ${response.status} - ${String(errorData.description) !== '' ? String(errorData.description) : response.statusText}`);
|
|
1364
|
+
}
|
|
1365
|
+
const data = await response.json();
|
|
1366
|
+
if (data.status === 'error') {
|
|
1367
|
+
throw new Error((_a = data.description) !== null && _a !== void 0 ? _a : 'Failed to get permission');
|
|
1368
|
+
}
|
|
1369
|
+
return data.permission;
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* @method getMessageBoxQuote
|
|
1373
|
+
* @async
|
|
1374
|
+
* @param {GetQuoteParams} params - Quote request parameters
|
|
1375
|
+
* @returns {Promise<MessageBoxQuote>} Fee quote and permission status
|
|
1376
|
+
*
|
|
1377
|
+
* @description
|
|
1378
|
+
* Gets a fee quote for sending a message, including delivery and recipient fees.
|
|
1379
|
+
*
|
|
1380
|
+
* @example
|
|
1381
|
+
* const quote = await client.getMessageBoxQuote({
|
|
1382
|
+
* recipient: '03def456...',
|
|
1383
|
+
* messageBox: 'notifications'
|
|
1384
|
+
* })
|
|
1385
|
+
*/
|
|
1386
|
+
async getMessageBoxQuote(params, overrideHost) {
|
|
1387
|
+
var _a;
|
|
1388
|
+
await this.assertInitialized();
|
|
1389
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : await this.resolveHostForRecipient(params.recipient);
|
|
1390
|
+
const queryParams = new URLSearchParams({
|
|
1391
|
+
recipient: params.recipient,
|
|
1392
|
+
messageBox: params.messageBox
|
|
1393
|
+
});
|
|
1394
|
+
Logger.log('[MB CLIENT] Getting messageBox quote...');
|
|
1395
|
+
const response = await this.authFetch.fetch(`${finalHost}/permissions/quote?${queryParams.toString()}`, {
|
|
1396
|
+
method: 'GET'
|
|
1397
|
+
});
|
|
1398
|
+
if (!response.ok) {
|
|
1399
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1400
|
+
throw new Error(`Failed to get quote: HTTP ${response.status} - ${(_a = String(errorData.description)) !== null && _a !== void 0 ? _a : response.statusText}`);
|
|
1401
|
+
}
|
|
1402
|
+
const { status, description, quote } = await response.json();
|
|
1403
|
+
if (status === 'error') {
|
|
1404
|
+
throw new Error(description !== null && description !== void 0 ? description : 'Failed to get quote');
|
|
1405
|
+
}
|
|
1406
|
+
const deliveryAgentIdentityKey = response.headers.get('x-bsv-auth-identity-key');
|
|
1407
|
+
if (deliveryAgentIdentityKey == null) {
|
|
1408
|
+
throw new Error('Failed to get quote: Delivery agent did not provide their identity key');
|
|
1409
|
+
}
|
|
1410
|
+
return {
|
|
1411
|
+
recipientFee: quote.recipientFee,
|
|
1412
|
+
deliveryFee: quote.deliveryFee,
|
|
1413
|
+
deliveryAgentIdentityKey
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* @method listMessageBoxPermissions
|
|
1418
|
+
* @async
|
|
1419
|
+
* @param {ListPermissionsParams} [params] - Optional filtering and pagination parameters
|
|
1420
|
+
* @returns {Promise<MessageBoxPermission[]>} List of current permissions
|
|
1421
|
+
*
|
|
1422
|
+
* @description
|
|
1423
|
+
* Lists permissions for the authenticated user's messageBoxes with optional pagination.
|
|
1424
|
+
*
|
|
1425
|
+
* @example
|
|
1426
|
+
* // List all permissions
|
|
1427
|
+
* const all = await client.listMessageBoxPermissions()
|
|
1428
|
+
*
|
|
1429
|
+
* // List only notification permissions with pagination
|
|
1430
|
+
* const notifications = await client.listMessageBoxPermissions({
|
|
1431
|
+
* messageBox: 'notifications',
|
|
1432
|
+
* limit: 50,
|
|
1433
|
+
* offset: 0
|
|
1434
|
+
* })
|
|
1435
|
+
*/
|
|
1436
|
+
async listMessageBoxPermissions(params, overrideHost) {
|
|
1437
|
+
var _a;
|
|
1438
|
+
await this.assertInitialized();
|
|
1439
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
|
|
1440
|
+
const queryParams = new URLSearchParams();
|
|
1441
|
+
if ((params === null || params === void 0 ? void 0 : params.messageBox) != null) {
|
|
1442
|
+
queryParams.set('message_box', params.messageBox);
|
|
1443
|
+
}
|
|
1444
|
+
if ((params === null || params === void 0 ? void 0 : params.limit) !== undefined) {
|
|
1445
|
+
queryParams.set('limit', params.limit.toString());
|
|
1446
|
+
}
|
|
1447
|
+
if ((params === null || params === void 0 ? void 0 : params.offset) !== undefined) {
|
|
1448
|
+
queryParams.set('offset', params.offset.toString());
|
|
1449
|
+
}
|
|
1450
|
+
Logger.log('[MB CLIENT] Listing messageBox permissions with params:', queryParams.toString());
|
|
1451
|
+
const response = await this.authFetch.fetch(`${finalHost}/permissions/list?${queryParams.toString()}`, {
|
|
1452
|
+
method: 'GET'
|
|
1453
|
+
});
|
|
1454
|
+
if (!response.ok) {
|
|
1455
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1456
|
+
throw new Error(`Failed to list permissions: HTTP ${response.status} - ${String(errorData.description) !== '' ? String(errorData.description) : response.statusText}`);
|
|
1457
|
+
}
|
|
1458
|
+
const data = await response.json();
|
|
1459
|
+
if (data.status === 'error') {
|
|
1460
|
+
throw new Error((_a = data.description) !== null && _a !== void 0 ? _a : 'Failed to list permissions');
|
|
1461
|
+
}
|
|
1462
|
+
return data.permissions.map((p) => ({
|
|
1463
|
+
sender: p.sender,
|
|
1464
|
+
messageBox: p.message_box,
|
|
1465
|
+
recipientFee: p.recipient_fee,
|
|
1466
|
+
status: MessageBoxClient.getStatusFromFee(p.recipient_fee),
|
|
1467
|
+
createdAt: p.created_at,
|
|
1468
|
+
updatedAt: p.updated_at
|
|
1469
|
+
}));
|
|
1470
|
+
}
|
|
1471
|
+
// ===========================
|
|
1472
|
+
// NOTIFICATION CONVENIENCE METHODS
|
|
1473
|
+
// ===========================
|
|
1474
|
+
/**
|
|
1475
|
+
* @method allowNotificationsFromPeer
|
|
1476
|
+
* @async
|
|
1477
|
+
* @param {PubKeyHex} identityKey - Sender's identity key to allow
|
|
1478
|
+
* @param {number} [recipientFee=0] - Fee to charge (0 for always allow)
|
|
1479
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1480
|
+
* @returns {Promise<void>} Permission status after allowing
|
|
1481
|
+
*
|
|
1482
|
+
* @description
|
|
1483
|
+
* Convenience method to allow notifications from a specific peer.
|
|
1484
|
+
*
|
|
1485
|
+
* @example
|
|
1486
|
+
* await client.allowNotificationsFromPeer('03abc123...') // Always allow
|
|
1487
|
+
* await client.allowNotificationsFromPeer('03def456...', 5) // Allow for 5 sats
|
|
1488
|
+
*/
|
|
1489
|
+
async allowNotificationsFromPeer(identityKey, recipientFee = 0, overrideHost) {
|
|
1490
|
+
await this.setMessageBoxPermission({
|
|
1491
|
+
messageBox: 'notifications',
|
|
1492
|
+
sender: identityKey,
|
|
1493
|
+
recipientFee
|
|
1494
|
+
}, overrideHost);
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* @method denyNotificationsFromPeer
|
|
1498
|
+
* @async
|
|
1499
|
+
* @param {PubKeyHex} identityKey - Sender's identity key to block
|
|
1500
|
+
* @returns {Promise<void>} Permission status after denying
|
|
1501
|
+
*
|
|
1502
|
+
* @description
|
|
1503
|
+
* Convenience method to block notifications from a specific peer.
|
|
1504
|
+
*
|
|
1505
|
+
* @example
|
|
1506
|
+
* await client.denyNotificationsFromPeer('03spam123...')
|
|
1507
|
+
*/
|
|
1508
|
+
async denyNotificationsFromPeer(identityKey, overrideHost) {
|
|
1509
|
+
await this.setMessageBoxPermission({
|
|
1510
|
+
messageBox: 'notifications',
|
|
1511
|
+
sender: identityKey,
|
|
1512
|
+
recipientFee: -1
|
|
1513
|
+
}, overrideHost);
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* @method checkPeerNotificationStatus
|
|
1517
|
+
* @async
|
|
1518
|
+
* @param {PubKeyHex} identityKey - Sender's identity key to check
|
|
1519
|
+
* @returns {Promise<MessageBoxPermission>} Current permission status
|
|
1520
|
+
*
|
|
1521
|
+
* @description
|
|
1522
|
+
* Convenience method to check notification permission for a specific peer.
|
|
1523
|
+
*
|
|
1524
|
+
* @example
|
|
1525
|
+
* const status = await client.checkPeerNotificationStatus('03abc123...')
|
|
1526
|
+
* console.log(status.allowed) // true/false
|
|
1527
|
+
*/
|
|
1528
|
+
async checkPeerNotificationStatus(identityKey, overrideHost) {
|
|
1529
|
+
const myIdentityKey = await this.getIdentityKey();
|
|
1530
|
+
return await this.getMessageBoxPermission({
|
|
1531
|
+
recipient: myIdentityKey,
|
|
1532
|
+
messageBox: 'notifications',
|
|
1533
|
+
sender: identityKey
|
|
1534
|
+
}, overrideHost);
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* @method listPeerNotifications
|
|
1538
|
+
* @async
|
|
1539
|
+
* @returns {Promise<MessageBoxPermission[]>} List of notification permissions
|
|
1540
|
+
*
|
|
1541
|
+
* @description
|
|
1542
|
+
* Convenience method to list all notification permissions.
|
|
1543
|
+
*
|
|
1544
|
+
* @example
|
|
1545
|
+
* const notifications = await client.listPeerNotifications()
|
|
1546
|
+
*/
|
|
1547
|
+
async listPeerNotifications(overrideHost) {
|
|
1548
|
+
return await this.listMessageBoxPermissions({ messageBox: 'notifications' }, overrideHost);
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* @method sendNotification
|
|
1552
|
+
* @async
|
|
1553
|
+
* @param {PubKeyHex} recipient - Recipient's identity key
|
|
1554
|
+
* @param {string | object} body - Notification content
|
|
1555
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1556
|
+
* @returns {Promise<SendMessageResponse>} Send result
|
|
1557
|
+
*
|
|
1558
|
+
* @description
|
|
1559
|
+
* Convenience method to send a notification with automatic quote fetching and payment handling.
|
|
1560
|
+
* Automatically determines the required payment amount and creates the payment if needed.
|
|
1561
|
+
*
|
|
1562
|
+
* @example
|
|
1563
|
+
* // Send notification (auto-determines payment needed)
|
|
1564
|
+
* await client.sendNotification('03def456...', 'Hello!')
|
|
1565
|
+
*
|
|
1566
|
+
* // Send with maximum payment limit for safety
|
|
1567
|
+
* await client.sendNotification('03def456...', { title: 'Alert', body: 'Important update' }, 50)
|
|
1568
|
+
*/
|
|
1569
|
+
async sendNotification(recipient, body, overrideHost) {
|
|
1570
|
+
await this.assertInitialized();
|
|
1571
|
+
// Use sendMessage with permission checking enabled
|
|
1572
|
+
// This eliminates duplication of quote fetching and payment logic
|
|
1573
|
+
return await this.sendMessage({
|
|
1574
|
+
recipient,
|
|
1575
|
+
messageBox: 'notifications',
|
|
1576
|
+
body,
|
|
1577
|
+
checkPermissions: true
|
|
1578
|
+
}, overrideHost);
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Register a device for FCM push notifications.
|
|
1582
|
+
*
|
|
1583
|
+
* @async
|
|
1584
|
+
* @param {DeviceRegistrationParams} params - Device registration parameters
|
|
1585
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1586
|
+
* @returns {Promise<DeviceRegistrationResponse>} Registration response
|
|
1587
|
+
*
|
|
1588
|
+
* @description
|
|
1589
|
+
* Registers a device with the message box server to receive FCM push notifications.
|
|
1590
|
+
* The FCM token is obtained from Firebase SDK on the client side.
|
|
1591
|
+
*
|
|
1592
|
+
* @example
|
|
1593
|
+
* const result = await client.registerDevice({
|
|
1594
|
+
* fcmToken: 'eBo8F...',
|
|
1595
|
+
* platform: 'ios',
|
|
1596
|
+
* deviceId: 'iPhone15Pro'
|
|
1597
|
+
* })
|
|
1598
|
+
*/
|
|
1599
|
+
async registerDevice(params, overrideHost) {
|
|
1600
|
+
var _a, _b, _c, _d, _e;
|
|
1601
|
+
await this.assertInitialized();
|
|
1602
|
+
if (params.fcmToken == null || params.fcmToken.trim() === '') {
|
|
1603
|
+
throw new Error('fcmToken is required and must be a non-empty string');
|
|
1604
|
+
}
|
|
1605
|
+
// Validate platform if provided
|
|
1606
|
+
const validPlatforms = ['ios', 'android', 'web'];
|
|
1607
|
+
if (params.platform != null && !validPlatforms.includes(params.platform)) {
|
|
1608
|
+
throw new Error('platform must be one of: ios, android, web');
|
|
1609
|
+
}
|
|
1610
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
|
|
1611
|
+
Logger.log('[MB CLIENT] Registering device for FCM notifications...');
|
|
1612
|
+
const response = await this.authFetch.fetch(`${finalHost}/registerDevice`, {
|
|
1613
|
+
method: 'POST',
|
|
1614
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1615
|
+
body: JSON.stringify({
|
|
1616
|
+
fcmToken: params.fcmToken.trim(),
|
|
1617
|
+
deviceId: (_b = (_a = params.deviceId) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : undefined,
|
|
1618
|
+
platform: (_c = params.platform) !== null && _c !== void 0 ? _c : undefined
|
|
1619
|
+
})
|
|
1620
|
+
});
|
|
1621
|
+
if (!response.ok) {
|
|
1622
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1623
|
+
const description = (_d = String(errorData.description)) !== null && _d !== void 0 ? _d : response.statusText;
|
|
1624
|
+
throw new Error(`Failed to register device: HTTP ${response.status} - ${description}`);
|
|
1625
|
+
}
|
|
1626
|
+
const data = await response.json();
|
|
1627
|
+
if (data.status === 'error') {
|
|
1628
|
+
throw new Error((_e = data.description) !== null && _e !== void 0 ? _e : 'Failed to register device');
|
|
1629
|
+
}
|
|
1630
|
+
Logger.log('[MB CLIENT] Device registered successfully');
|
|
1631
|
+
return {
|
|
1632
|
+
status: data.status,
|
|
1633
|
+
message: data.message,
|
|
1634
|
+
deviceId: data.deviceId
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* List all registered devices for push notifications.
|
|
1639
|
+
*
|
|
1640
|
+
* @async
|
|
1641
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1642
|
+
* @returns {Promise<RegisteredDevice[]>} Array of registered devices
|
|
1643
|
+
*
|
|
1644
|
+
* @description
|
|
1645
|
+
* Retrieves all devices registered by the authenticated user for FCM push notifications.
|
|
1646
|
+
* Only shows devices belonging to the current user (authenticated via AuthFetch).
|
|
1647
|
+
*
|
|
1648
|
+
* @example
|
|
1649
|
+
* const devices = await client.listRegisteredDevices()
|
|
1650
|
+
* console.log(`Found ${devices.length} registered devices`)
|
|
1651
|
+
* devices.forEach(device => {
|
|
1652
|
+
* console.log(`Device: ${device.platform} - ${device.fcmToken}`)
|
|
1653
|
+
* })
|
|
1654
|
+
*/
|
|
1655
|
+
async listRegisteredDevices(overrideHost) {
|
|
1656
|
+
var _a, _b;
|
|
1657
|
+
await this.assertInitialized();
|
|
1658
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
|
|
1659
|
+
Logger.log('[MB CLIENT] Listing registered devices...');
|
|
1660
|
+
const response = await this.authFetch.fetch(`${finalHost}/devices`, {
|
|
1661
|
+
method: 'GET'
|
|
1662
|
+
});
|
|
1663
|
+
if (!response.ok) {
|
|
1664
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1665
|
+
const description = (_a = String(errorData.description)) !== null && _a !== void 0 ? _a : response.statusText;
|
|
1666
|
+
throw new Error(`Failed to list devices: HTTP ${response.status} - ${description}`);
|
|
1667
|
+
}
|
|
1668
|
+
const data = await response.json();
|
|
1669
|
+
if (data.status === 'error') {
|
|
1670
|
+
throw new Error((_b = data.description) !== null && _b !== void 0 ? _b : 'Failed to list devices');
|
|
1671
|
+
}
|
|
1672
|
+
Logger.log(`[MB CLIENT] Found ${data.devices.length} registered devices`);
|
|
1673
|
+
return data.devices;
|
|
1674
|
+
}
|
|
1675
|
+
// ===========================
|
|
1676
|
+
// PRIVATE HELPER METHODS
|
|
1677
|
+
// ===========================
|
|
1678
|
+
static getStatusFromFee(fee) {
|
|
1679
|
+
if (fee === -1)
|
|
1680
|
+
return 'blocked';
|
|
1681
|
+
if (fee === 0)
|
|
1682
|
+
return 'always_allow';
|
|
1683
|
+
return 'payment_required';
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Creates payment transaction for message delivery fees
|
|
1687
|
+
* TODO: Consider consolidating payment generating logic with a util PeerPayClient can use as well.
|
|
1688
|
+
* @private
|
|
1689
|
+
* @param {string} recipient - Recipient identity key
|
|
1690
|
+
* @param {MessageBoxQuote} quote - Fee quote with delivery and recipient fees
|
|
1691
|
+
* @param {string} description - Description for the payment transaction
|
|
1692
|
+
* @returns {Promise<Payment>} Payment transaction data
|
|
1693
|
+
*/
|
|
1694
|
+
async createMessagePayment(recipient, quote, description = 'MessageBox delivery payment') {
|
|
1695
|
+
if (quote.recipientFee <= 0 && quote.deliveryFee <= 0) {
|
|
1696
|
+
throw new Error('No payment required');
|
|
1697
|
+
}
|
|
1698
|
+
Logger.log(`[MB CLIENT] Creating payment transaction for ${quote.recipientFee} sats (delivery: ${quote.deliveryFee}, recipient: ${quote.recipientFee})`);
|
|
1699
|
+
const outputs = [];
|
|
1700
|
+
const createActionOutputs = [];
|
|
1701
|
+
// Get sender identity key for remittance data
|
|
1702
|
+
const senderIdentityKey = await this.getIdentityKey();
|
|
1703
|
+
// Add server delivery fee output if > 0
|
|
1704
|
+
let outputIndex = 0;
|
|
1705
|
+
if (quote.deliveryFee > 0) {
|
|
1706
|
+
const derivationPrefix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
|
|
1707
|
+
const derivationSuffix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
|
|
1708
|
+
// Get host's derived public key
|
|
1709
|
+
const { publicKey: derivedKeyResult } = await this.walletClient.getPublicKey({
|
|
1710
|
+
protocolID: [2, '3241645161d8'],
|
|
1711
|
+
keyID: `${derivationPrefix} ${derivationSuffix}`,
|
|
1712
|
+
counterparty: quote.deliveryAgentIdentityKey
|
|
1713
|
+
});
|
|
1714
|
+
// Create locking script using host's public key
|
|
1715
|
+
const lockingScript = new sdk_1.P2PKH().lock(sdk_1.PublicKey.fromString(derivedKeyResult).toAddress()).toHex();
|
|
1716
|
+
// Add to createAction outputs
|
|
1717
|
+
createActionOutputs.push({
|
|
1718
|
+
satoshis: quote.deliveryFee,
|
|
1719
|
+
lockingScript,
|
|
1720
|
+
outputDescription: 'MessageBox server delivery fee',
|
|
1721
|
+
customInstructions: JSON.stringify({
|
|
1722
|
+
derivationPrefix,
|
|
1723
|
+
derivationSuffix,
|
|
1724
|
+
recipientIdentityKey: quote.deliveryAgentIdentityKey
|
|
1725
|
+
})
|
|
1726
|
+
});
|
|
1727
|
+
outputs.push({
|
|
1728
|
+
outputIndex: outputIndex++,
|
|
1729
|
+
protocol: 'wallet payment',
|
|
1730
|
+
paymentRemittance: {
|
|
1731
|
+
derivationPrefix,
|
|
1732
|
+
derivationSuffix,
|
|
1733
|
+
senderIdentityKey
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
// Add recipient fee output if > 0
|
|
1738
|
+
if (quote.recipientFee > 0) {
|
|
1739
|
+
const derivationPrefix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
|
|
1740
|
+
const derivationSuffix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
|
|
1741
|
+
// Get a derived public key for the recipient that "anyone" can verify
|
|
1742
|
+
const anyoneWallet = new sdk_1.ProtoWallet('anyone');
|
|
1743
|
+
const { publicKey: derivedKeyResult } = await anyoneWallet.getPublicKey({
|
|
1744
|
+
protocolID: [2, '3241645161d8'],
|
|
1745
|
+
keyID: `${derivationPrefix} ${derivationSuffix}`,
|
|
1746
|
+
counterparty: recipient
|
|
1747
|
+
});
|
|
1748
|
+
if (derivedKeyResult == null || derivedKeyResult.trim() === '') {
|
|
1749
|
+
throw new Error('Failed to derive recipient\'s public key');
|
|
1750
|
+
}
|
|
1751
|
+
// Create locking script using recipient's public key
|
|
1752
|
+
const lockingScript = new sdk_1.P2PKH().lock(sdk_1.PublicKey.fromString(derivedKeyResult).toAddress()).toHex();
|
|
1753
|
+
// Add to createAction outputs
|
|
1754
|
+
createActionOutputs.push({
|
|
1755
|
+
satoshis: quote.recipientFee,
|
|
1756
|
+
lockingScript,
|
|
1757
|
+
outputDescription: 'Recipient message fee',
|
|
1758
|
+
customInstructions: JSON.stringify({
|
|
1759
|
+
derivationPrefix,
|
|
1760
|
+
derivationSuffix,
|
|
1761
|
+
recipientIdentityKey: recipient
|
|
1762
|
+
})
|
|
1763
|
+
});
|
|
1764
|
+
outputs.push({
|
|
1765
|
+
outputIndex: outputIndex++,
|
|
1766
|
+
protocol: 'wallet payment',
|
|
1767
|
+
paymentRemittance: {
|
|
1768
|
+
derivationPrefix,
|
|
1769
|
+
derivationSuffix,
|
|
1770
|
+
senderIdentityKey
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
const { tx } = await this.walletClient.createAction({
|
|
1775
|
+
description,
|
|
1776
|
+
outputs: createActionOutputs,
|
|
1777
|
+
options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
|
|
1778
|
+
});
|
|
1779
|
+
if (tx == null) {
|
|
1780
|
+
throw new Error('Failed to create payment transaction');
|
|
1781
|
+
}
|
|
1782
|
+
return {
|
|
1783
|
+
tx,
|
|
1784
|
+
outputs,
|
|
1785
|
+
description
|
|
1786
|
+
// labels
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1169
1789
|
}
|
|
1170
1790
|
exports.MessageBoxClient = MessageBoxClient;
|
|
1171
1791
|
//# sourceMappingURL=MessageBoxClient.js.map
|