@bsv/message-box-client 1.1.10 → 1.2.0
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 +4 -4
- package/dist/cjs/src/MessageBoxClient.js +747 -129
- 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 +636 -57
- 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 +235 -24
- 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 +80 -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 +4 -4
- package/src/MessageBoxClient.ts +781 -68
- package/src/PeerPayClient.ts +11 -11
- package/src/Utils/logger.ts +17 -19
- package/src/types/permissions.ts +81 -0
- package/src/types.ts +87 -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
|
*
|
|
@@ -85,25 +118,26 @@ class MessageBoxClient {
|
|
|
85
118
|
var _a;
|
|
86
119
|
this.joinedRooms = new Set();
|
|
87
120
|
this.initialized = false;
|
|
88
|
-
const { host, walletClient, enableLogging = false, networkPreset = 'mainnet' } = options;
|
|
121
|
+
const { host, walletClient, enableLogging = false, networkPreset = 'mainnet', originator = undefined } = options;
|
|
89
122
|
const defaultHost = this.networkPreset === 'testnet'
|
|
90
123
|
? DEFAULT_TESTNET_HOST
|
|
91
124
|
: DEFAULT_MAINNET_HOST;
|
|
92
125
|
this.host = (_a = host === null || host === void 0 ? void 0 : host.trim()) !== null && _a !== void 0 ? _a : defaultHost;
|
|
93
|
-
this.walletClient = walletClient !== null && walletClient !== void 0 ? walletClient : new sdk_1.WalletClient();
|
|
126
|
+
this.walletClient = walletClient !== null && walletClient !== void 0 ? walletClient : new sdk_1.WalletClient('auto', originator);
|
|
94
127
|
this.authFetch = new sdk_1.AuthFetch(this.walletClient);
|
|
95
128
|
this.networkPreset = networkPreset;
|
|
96
129
|
this.lookupResolver = new sdk_1.LookupResolver({
|
|
97
130
|
networkPreset
|
|
98
131
|
});
|
|
99
132
|
if (enableLogging) {
|
|
100
|
-
|
|
133
|
+
Logger.enable();
|
|
101
134
|
}
|
|
102
135
|
}
|
|
103
136
|
/**
|
|
104
137
|
* @method init
|
|
105
138
|
* @async
|
|
106
139
|
* @param {string} [targetHost] - Optional host to set or override the default host.
|
|
140
|
+
* @param {string} [originator] - Optional originator to use with walletClient.
|
|
107
141
|
* @returns {Promise<void>}
|
|
108
142
|
*
|
|
109
143
|
* @description
|
|
@@ -122,7 +156,7 @@ class MessageBoxClient {
|
|
|
122
156
|
* await client.init()
|
|
123
157
|
* await client.sendMessage({ recipient, messageBox: 'inbox', body: 'Hello' })
|
|
124
158
|
*/
|
|
125
|
-
async init(targetHost = this.host) {
|
|
159
|
+
async init(targetHost = this.host, originator) {
|
|
126
160
|
var _a;
|
|
127
161
|
const normalizedHost = targetHost === null || targetHost === void 0 ? void 0 : targetHost.trim();
|
|
128
162
|
if (normalizedHost === '') {
|
|
@@ -136,13 +170,13 @@ class MessageBoxClient {
|
|
|
136
170
|
if (this.initialized)
|
|
137
171
|
return;
|
|
138
172
|
// 1. Get our identity key
|
|
139
|
-
const identityKey = await this.getIdentityKey();
|
|
173
|
+
const identityKey = await this.getIdentityKey(originator);
|
|
140
174
|
// 2. Check for any matching advertisements for the given host
|
|
141
|
-
const [firstAdvertisement] = await this.queryAdvertisements(identityKey, normalizedHost);
|
|
175
|
+
const [firstAdvertisement] = await this.queryAdvertisements(identityKey, normalizedHost, originator);
|
|
142
176
|
// 3. If none our found, anoint this host
|
|
143
177
|
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
|
-
|
|
145
|
-
const { txid } = await this.anointHost(normalizedHost);
|
|
178
|
+
Logger.log('[MB CLIENT] Anointing host:', normalizedHost);
|
|
179
|
+
const { txid } = await this.anointHost(normalizedHost, originator);
|
|
146
180
|
if (txid == null || txid.trim() === '') {
|
|
147
181
|
throw new Error('Failed to anoint host: No transaction ID returned');
|
|
148
182
|
}
|
|
@@ -177,24 +211,25 @@ class MessageBoxClient {
|
|
|
177
211
|
}
|
|
178
212
|
/**
|
|
179
213
|
* @method getIdentityKey
|
|
214
|
+
* @param {string} [originator] - Optional originator to use for identity key lookup
|
|
180
215
|
* @returns {Promise<string>} The identity public key of the user
|
|
181
216
|
* @description
|
|
182
217
|
* Returns the client's identity key, used for signing, encryption, and addressing.
|
|
183
218
|
* If not already loaded, it will fetch and cache it.
|
|
184
219
|
*/
|
|
185
|
-
async getIdentityKey() {
|
|
220
|
+
async getIdentityKey(originator) {
|
|
186
221
|
if (this.myIdentityKey != null && this.myIdentityKey.trim() !== '') {
|
|
187
222
|
return this.myIdentityKey;
|
|
188
223
|
}
|
|
189
|
-
|
|
224
|
+
Logger.log('[MB CLIENT] Fetching identity key...');
|
|
190
225
|
try {
|
|
191
|
-
const keyResult = await this.walletClient.getPublicKey({ identityKey: true });
|
|
226
|
+
const keyResult = await this.walletClient.getPublicKey({ identityKey: true }, originator);
|
|
192
227
|
this.myIdentityKey = keyResult.publicKey;
|
|
193
|
-
|
|
228
|
+
Logger.log(`[MB CLIENT] Identity key fetched: ${this.myIdentityKey}`);
|
|
194
229
|
return this.myIdentityKey;
|
|
195
230
|
}
|
|
196
231
|
catch (error) {
|
|
197
|
-
|
|
232
|
+
Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
|
|
198
233
|
throw new Error('Identity key retrieval failed');
|
|
199
234
|
}
|
|
200
235
|
}
|
|
@@ -214,6 +249,7 @@ class MessageBoxClient {
|
|
|
214
249
|
}
|
|
215
250
|
/**
|
|
216
251
|
* @method initializeConnection
|
|
252
|
+
* @param {string} [originator] - Optional originator to use for authentication.
|
|
217
253
|
* @async
|
|
218
254
|
* @returns {Promise<void>}
|
|
219
255
|
* @description
|
|
@@ -235,26 +271,17 @@ class MessageBoxClient {
|
|
|
235
271
|
* await mb.initializeConnection()
|
|
236
272
|
* // WebSocket is now ready for use
|
|
237
273
|
*/
|
|
238
|
-
async initializeConnection() {
|
|
274
|
+
async initializeConnection(originator) {
|
|
239
275
|
await this.assertInitialized();
|
|
240
|
-
|
|
276
|
+
Logger.log('[MB CLIENT] initializeConnection() STARTED');
|
|
241
277
|
if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
const keyResult = await this.walletClient.getPublicKey({ identityKey: true });
|
|
245
|
-
this.myIdentityKey = keyResult.publicKey;
|
|
246
|
-
logger_js_1.Logger.log(`[MB CLIENT] Identity key fetched successfully: ${this.myIdentityKey}`);
|
|
247
|
-
}
|
|
248
|
-
catch (error) {
|
|
249
|
-
logger_js_1.Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
|
|
250
|
-
throw new Error('Identity key retrieval failed');
|
|
251
|
-
}
|
|
278
|
+
await this.getIdentityKey(originator);
|
|
252
279
|
}
|
|
253
280
|
if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
|
|
254
|
-
|
|
281
|
+
Logger.error('[MB CLIENT ERROR] Identity key is still missing after retrieval!');
|
|
255
282
|
throw new Error('Identity key is missing');
|
|
256
283
|
}
|
|
257
|
-
|
|
284
|
+
Logger.log('[MB CLIENT] Setting up WebSocket connection...');
|
|
258
285
|
if (this.socket == null) {
|
|
259
286
|
if (typeof this.host !== 'string' || this.host.trim() === '') {
|
|
260
287
|
throw new Error('Cannot initialize WebSocket: Host is not set');
|
|
@@ -264,11 +291,11 @@ class MessageBoxClient {
|
|
|
264
291
|
let authenticated = false;
|
|
265
292
|
this.socket.on('connect', () => {
|
|
266
293
|
var _a;
|
|
267
|
-
|
|
294
|
+
Logger.log('[MB CLIENT] Connected to WebSocket.');
|
|
268
295
|
if (!identitySent) {
|
|
269
|
-
|
|
296
|
+
Logger.log('[MB CLIENT] Sending authentication data:', this.myIdentityKey);
|
|
270
297
|
if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
|
|
271
|
-
|
|
298
|
+
Logger.error('[MB CLIENT ERROR] Cannot send authentication: Identity key is missing!');
|
|
272
299
|
}
|
|
273
300
|
else {
|
|
274
301
|
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.emit('authenticated', { identityKey: this.myIdentityKey });
|
|
@@ -278,28 +305,28 @@ class MessageBoxClient {
|
|
|
278
305
|
});
|
|
279
306
|
// Listen for authentication success from the server
|
|
280
307
|
this.socket.on('authenticationSuccess', (data) => {
|
|
281
|
-
|
|
308
|
+
Logger.log(`[MB CLIENT] WebSocket authentication successful: ${JSON.stringify(data)}`);
|
|
282
309
|
authenticated = true;
|
|
283
310
|
});
|
|
284
311
|
// Handle authentication failures
|
|
285
312
|
this.socket.on('authenticationFailed', (data) => {
|
|
286
|
-
|
|
313
|
+
Logger.error(`[MB CLIENT ERROR] WebSocket authentication failed: ${JSON.stringify(data)}`);
|
|
287
314
|
authenticated = false;
|
|
288
315
|
});
|
|
289
316
|
this.socket.on('disconnect', () => {
|
|
290
|
-
|
|
317
|
+
Logger.log('[MB CLIENT] Disconnected from MessageBox server');
|
|
291
318
|
this.socket = undefined;
|
|
292
319
|
identitySent = false;
|
|
293
320
|
authenticated = false;
|
|
294
321
|
});
|
|
295
322
|
this.socket.on('error', (error) => {
|
|
296
|
-
|
|
323
|
+
Logger.error('[MB CLIENT ERROR] WebSocket error:', error);
|
|
297
324
|
});
|
|
298
325
|
// Wait for authentication confirmation before proceeding
|
|
299
326
|
await new Promise((resolve, reject) => {
|
|
300
327
|
setTimeout(() => {
|
|
301
328
|
if (authenticated) {
|
|
302
|
-
|
|
329
|
+
Logger.log('[MB CLIENT] WebSocket fully authenticated and ready!');
|
|
303
330
|
resolve();
|
|
304
331
|
}
|
|
305
332
|
else {
|
|
@@ -313,6 +340,7 @@ class MessageBoxClient {
|
|
|
313
340
|
* @method resolveHostForRecipient
|
|
314
341
|
* @async
|
|
315
342
|
* @param {string} identityKey - The public identity key of the intended recipient.
|
|
343
|
+
* @param {string} [originator] - The originator to use for the WalletClient.
|
|
316
344
|
* @returns {Promise<string>} - A fully qualified host URL for the recipient's MessageBox server.
|
|
317
345
|
*
|
|
318
346
|
* @description
|
|
@@ -327,10 +355,10 @@ class MessageBoxClient {
|
|
|
327
355
|
* @example
|
|
328
356
|
* const host = await resolveHostForRecipient('028d...') // → returns either overlay host or this.host
|
|
329
357
|
*/
|
|
330
|
-
async resolveHostForRecipient(identityKey) {
|
|
331
|
-
const advertisementTokens = await this.queryAdvertisements(identityKey);
|
|
358
|
+
async resolveHostForRecipient(identityKey, originator) {
|
|
359
|
+
const advertisementTokens = await this.queryAdvertisements(identityKey, undefined, originator);
|
|
332
360
|
if (advertisementTokens.length === 0) {
|
|
333
|
-
|
|
361
|
+
Logger.warn(`[MB CLIENT] No advertisements for ${identityKey}, using default host ${this.host}`);
|
|
334
362
|
return this.host;
|
|
335
363
|
}
|
|
336
364
|
// Return the first host found
|
|
@@ -344,10 +372,10 @@ class MessageBoxClient {
|
|
|
344
372
|
* @param host? if passed, only look for adverts anointed at that host
|
|
345
373
|
* @returns 0-length array if nothing valid was found
|
|
346
374
|
*/
|
|
347
|
-
async queryAdvertisements(identityKey, host) {
|
|
375
|
+
async queryAdvertisements(identityKey, host, originator) {
|
|
348
376
|
const hosts = [];
|
|
349
377
|
try {
|
|
350
|
-
const query = { identityKey: identityKey !== null && identityKey !== void 0 ? identityKey : await this.getIdentityKey() };
|
|
378
|
+
const query = { identityKey: identityKey !== null && identityKey !== void 0 ? identityKey : await this.getIdentityKey(originator) };
|
|
351
379
|
if (host != null && host.trim() !== '')
|
|
352
380
|
query.host = host;
|
|
353
381
|
const result = await this.lookupResolver.query({
|
|
@@ -355,7 +383,7 @@ class MessageBoxClient {
|
|
|
355
383
|
query
|
|
356
384
|
});
|
|
357
385
|
if (result.type !== 'output-list') {
|
|
358
|
-
throw new Error(`Unexpected result type: ${result.type}`);
|
|
386
|
+
throw new Error(`Unexpected result type: ${String(result.type)}`);
|
|
359
387
|
}
|
|
360
388
|
for (const output of result.outputs) {
|
|
361
389
|
try {
|
|
@@ -380,7 +408,7 @@ class MessageBoxClient {
|
|
|
380
408
|
}
|
|
381
409
|
}
|
|
382
410
|
catch (err) {
|
|
383
|
-
|
|
411
|
+
Logger.error('[MB CLIENT ERROR] _queryAdvertisements failed:', err);
|
|
384
412
|
}
|
|
385
413
|
return hosts;
|
|
386
414
|
}
|
|
@@ -406,10 +434,10 @@ class MessageBoxClient {
|
|
|
406
434
|
async joinRoom(messageBox) {
|
|
407
435
|
var _a, _b;
|
|
408
436
|
await this.assertInitialized();
|
|
409
|
-
|
|
437
|
+
Logger.log(`[MB CLIENT] Attempting to join WebSocket room: ${messageBox}`);
|
|
410
438
|
// Ensure WebSocket connection is established first
|
|
411
439
|
if (this.socket == null) {
|
|
412
|
-
|
|
440
|
+
Logger.log('[MB CLIENT] No WebSocket connection. Initializing...');
|
|
413
441
|
await this.initializeConnection();
|
|
414
442
|
}
|
|
415
443
|
if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
|
|
@@ -417,17 +445,17 @@ class MessageBoxClient {
|
|
|
417
445
|
}
|
|
418
446
|
const roomId = `${(_a = this.myIdentityKey) !== null && _a !== void 0 ? _a : ''}-${messageBox}`;
|
|
419
447
|
if (this.joinedRooms.has(roomId)) {
|
|
420
|
-
|
|
448
|
+
Logger.log(`[MB CLIENT] Already joined WebSocket room: ${roomId}`);
|
|
421
449
|
return;
|
|
422
450
|
}
|
|
423
451
|
try {
|
|
424
|
-
|
|
452
|
+
Logger.log(`[MB CLIENT] Joining WebSocket room: ${roomId}`);
|
|
425
453
|
await ((_b = this.socket) === null || _b === void 0 ? void 0 : _b.emit('joinRoom', roomId));
|
|
426
454
|
this.joinedRooms.add(roomId);
|
|
427
|
-
|
|
455
|
+
Logger.log(`[MB CLIENT] Successfully joined room: ${roomId}`);
|
|
428
456
|
}
|
|
429
457
|
catch (error) {
|
|
430
|
-
|
|
458
|
+
Logger.error(`[MB CLIENT ERROR] Failed to join WebSocket room: ${roomId}`, error);
|
|
431
459
|
}
|
|
432
460
|
}
|
|
433
461
|
/**
|
|
@@ -458,10 +486,10 @@ class MessageBoxClient {
|
|
|
458
486
|
* onMessage: (msg) => console.log('Received live message:', msg)
|
|
459
487
|
* })
|
|
460
488
|
*/
|
|
461
|
-
async listenForLiveMessages({ onMessage, messageBox }) {
|
|
489
|
+
async listenForLiveMessages({ onMessage, messageBox, originator }) {
|
|
462
490
|
var _a;
|
|
463
491
|
await this.assertInitialized();
|
|
464
|
-
|
|
492
|
+
Logger.log(`[MB CLIENT] Setting up listener for WebSocket room: ${messageBox}`);
|
|
465
493
|
// Ensure WebSocket connection and room join
|
|
466
494
|
await this.joinRoom(messageBox);
|
|
467
495
|
// Ensure identity key is available before creating roomId
|
|
@@ -469,10 +497,10 @@ class MessageBoxClient {
|
|
|
469
497
|
throw new Error('[MB CLIENT ERROR] Identity key is missing. Cannot construct room ID.');
|
|
470
498
|
}
|
|
471
499
|
const roomId = `${this.myIdentityKey}-${messageBox}`;
|
|
472
|
-
|
|
500
|
+
Logger.log(`[MB CLIENT] Listening for messages in room: ${roomId}`);
|
|
473
501
|
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.on(`sendMessage-${roomId}`, (message) => {
|
|
474
502
|
void (async () => {
|
|
475
|
-
|
|
503
|
+
Logger.log(`[MB CLIENT] Received message in room ${roomId}:`, message);
|
|
476
504
|
try {
|
|
477
505
|
let parsedBody = message.body;
|
|
478
506
|
if (typeof parsedBody === 'string') {
|
|
@@ -486,17 +514,17 @@ class MessageBoxClient {
|
|
|
486
514
|
if (parsedBody != null &&
|
|
487
515
|
typeof parsedBody === 'object' &&
|
|
488
516
|
typeof parsedBody.encryptedMessage === 'string') {
|
|
489
|
-
|
|
517
|
+
Logger.log(`[MB CLIENT] Decrypting message from ${String(message.sender)}...`);
|
|
490
518
|
const decrypted = await this.walletClient.decrypt({
|
|
491
519
|
protocolID: [1, 'messagebox'],
|
|
492
520
|
keyID: '1',
|
|
493
521
|
counterparty: message.sender,
|
|
494
522
|
ciphertext: sdk_1.Utils.toArray(parsedBody.encryptedMessage, 'base64')
|
|
495
|
-
});
|
|
523
|
+
}, originator);
|
|
496
524
|
message.body = sdk_1.Utils.toUTF8(decrypted.plaintext);
|
|
497
525
|
}
|
|
498
526
|
else {
|
|
499
|
-
|
|
527
|
+
Logger.log('[MB CLIENT] Message is not encrypted.');
|
|
500
528
|
message.body = typeof parsedBody === 'string'
|
|
501
529
|
? parsedBody
|
|
502
530
|
: (() => { try {
|
|
@@ -508,7 +536,7 @@ class MessageBoxClient {
|
|
|
508
536
|
}
|
|
509
537
|
}
|
|
510
538
|
catch (err) {
|
|
511
|
-
|
|
539
|
+
Logger.error('[MB CLIENT ERROR] Failed to parse or decrypt live message:', err);
|
|
512
540
|
message.body = '[Error: Failed to decrypt or parse message]';
|
|
513
541
|
}
|
|
514
542
|
onMessage(message);
|
|
@@ -544,7 +572,7 @@ class MessageBoxClient {
|
|
|
544
572
|
* body: { amount: 1000 }
|
|
545
573
|
* })
|
|
546
574
|
*/
|
|
547
|
-
async sendLiveMessage({ recipient, messageBox, body, messageId, skipEncryption }) {
|
|
575
|
+
async sendLiveMessage({ recipient, messageBox, body, messageId, skipEncryption, checkPermissions, originator }) {
|
|
548
576
|
await this.assertInitialized();
|
|
549
577
|
if (recipient == null || recipient.trim() === '') {
|
|
550
578
|
throw new Error('[MB CLIENT ERROR] Recipient identity key is required');
|
|
@@ -559,7 +587,7 @@ class MessageBoxClient {
|
|
|
559
587
|
await this.joinRoom(messageBox);
|
|
560
588
|
// Fallback to HTTP if WebSocket is not connected
|
|
561
589
|
if (this.socket == null || !this.socket.connected) {
|
|
562
|
-
|
|
590
|
+
Logger.warn('[MB CLIENT WARNING] WebSocket not connected, falling back to HTTP');
|
|
563
591
|
const targetHost = await this.resolveHostForRecipient(recipient);
|
|
564
592
|
return await this.sendMessage({ recipient, messageBox, body }, targetHost);
|
|
565
593
|
}
|
|
@@ -570,15 +598,15 @@ class MessageBoxClient {
|
|
|
570
598
|
protocolID: [1, 'messagebox'],
|
|
571
599
|
keyID: '1',
|
|
572
600
|
counterparty: recipient
|
|
573
|
-
});
|
|
601
|
+
}, originator);
|
|
574
602
|
finalMessageId = messageId !== null && messageId !== void 0 ? messageId : Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
575
603
|
}
|
|
576
604
|
catch (error) {
|
|
577
|
-
|
|
605
|
+
Logger.error('[MB CLIENT ERROR] Failed to generate HMAC:', error);
|
|
578
606
|
throw new Error('Failed to generate message identifier.');
|
|
579
607
|
}
|
|
580
608
|
const roomId = `${recipient}-${messageBox}`;
|
|
581
|
-
|
|
609
|
+
Logger.log(`[MB CLIENT] Sending WebSocket message to room: ${roomId}`);
|
|
582
610
|
let outgoingBody;
|
|
583
611
|
if (skipEncryption === true) {
|
|
584
612
|
outgoingBody = typeof body === 'string' ? body : JSON.stringify(body);
|
|
@@ -589,7 +617,7 @@ class MessageBoxClient {
|
|
|
589
617
|
keyID: '1',
|
|
590
618
|
counterparty: recipient,
|
|
591
619
|
plaintext: sdk_1.Utils.toArray(typeof body === 'string' ? body : JSON.stringify(body), 'utf8')
|
|
592
|
-
});
|
|
620
|
+
}, originator);
|
|
593
621
|
outgoingBody = JSON.stringify({
|
|
594
622
|
encryptedMessage: sdk_1.Utils.toBase64(encryptedMessage.ciphertext)
|
|
595
623
|
});
|
|
@@ -606,15 +634,16 @@ class MessageBoxClient {
|
|
|
606
634
|
if (typeof (socketAny === null || socketAny === void 0 ? void 0 : socketAny.off) === 'function') {
|
|
607
635
|
socketAny.off(ackEvent, ackHandler);
|
|
608
636
|
}
|
|
609
|
-
|
|
637
|
+
Logger.log('[MB CLIENT] Received WebSocket acknowledgment:', response);
|
|
610
638
|
if (response == null || response.status !== 'success') {
|
|
611
|
-
|
|
639
|
+
Logger.warn('[MB CLIENT] WebSocket message failed or returned unexpected response. Falling back to HTTP.');
|
|
612
640
|
const fallbackMessage = {
|
|
613
641
|
recipient,
|
|
614
642
|
messageBox,
|
|
615
643
|
body,
|
|
616
644
|
messageId: finalMessageId,
|
|
617
|
-
skipEncryption
|
|
645
|
+
skipEncryption,
|
|
646
|
+
checkPermissions
|
|
618
647
|
};
|
|
619
648
|
this.resolveHostForRecipient(recipient)
|
|
620
649
|
.then(async (host) => {
|
|
@@ -624,7 +653,7 @@ class MessageBoxClient {
|
|
|
624
653
|
.catch(reject);
|
|
625
654
|
}
|
|
626
655
|
else {
|
|
627
|
-
|
|
656
|
+
Logger.log('[MB CLIENT] Message sent successfully via WebSocket:', response);
|
|
628
657
|
resolve(response);
|
|
629
658
|
}
|
|
630
659
|
};
|
|
@@ -647,13 +676,14 @@ class MessageBoxClient {
|
|
|
647
676
|
if (typeof (socketAny === null || socketAny === void 0 ? void 0 : socketAny.off) === 'function') {
|
|
648
677
|
socketAny.off(ackEvent, ackHandler);
|
|
649
678
|
}
|
|
650
|
-
|
|
679
|
+
Logger.warn('[CLIENT] WebSocket acknowledgment timed out, falling back to HTTP');
|
|
651
680
|
const fallbackMessage = {
|
|
652
681
|
recipient,
|
|
653
682
|
messageBox,
|
|
654
683
|
body,
|
|
655
684
|
messageId: finalMessageId,
|
|
656
|
-
skipEncryption
|
|
685
|
+
skipEncryption,
|
|
686
|
+
checkPermissions
|
|
657
687
|
};
|
|
658
688
|
this.resolveHostForRecipient(recipient)
|
|
659
689
|
.then(async (host) => {
|
|
@@ -683,14 +713,14 @@ class MessageBoxClient {
|
|
|
683
713
|
async leaveRoom(messageBox) {
|
|
684
714
|
await this.assertInitialized();
|
|
685
715
|
if (this.socket == null) {
|
|
686
|
-
|
|
716
|
+
Logger.warn('[MB CLIENT] Attempted to leave a room but WebSocket is not connected.');
|
|
687
717
|
return;
|
|
688
718
|
}
|
|
689
719
|
if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
|
|
690
720
|
throw new Error('[MB CLIENT ERROR] Identity key is not defined');
|
|
691
721
|
}
|
|
692
722
|
const roomId = `${this.myIdentityKey}-${messageBox}`;
|
|
693
|
-
|
|
723
|
+
Logger.log(`[MB CLIENT] Leaving WebSocket room: ${roomId}`);
|
|
694
724
|
this.socket.emit('leaveRoom', roomId);
|
|
695
725
|
// Ensure the room is removed from tracking
|
|
696
726
|
this.joinedRooms.delete(roomId);
|
|
@@ -711,12 +741,12 @@ class MessageBoxClient {
|
|
|
711
741
|
async disconnectWebSocket() {
|
|
712
742
|
await this.assertInitialized();
|
|
713
743
|
if (this.socket != null) {
|
|
714
|
-
|
|
744
|
+
Logger.log('[MB CLIENT] Closing WebSocket connection...');
|
|
715
745
|
this.socket.disconnect();
|
|
716
746
|
this.socket = undefined;
|
|
717
747
|
}
|
|
718
748
|
else {
|
|
719
|
-
|
|
749
|
+
Logger.log('[MB CLIENT] No active WebSocket connection to close.');
|
|
720
750
|
}
|
|
721
751
|
}
|
|
722
752
|
/**
|
|
@@ -746,7 +776,7 @@ class MessageBoxClient {
|
|
|
746
776
|
* body: { type: 'ping' }
|
|
747
777
|
* })
|
|
748
778
|
*/
|
|
749
|
-
async sendMessage(message, overrideHost) {
|
|
779
|
+
async sendMessage(message, overrideHost, originator) {
|
|
750
780
|
var _a, _b;
|
|
751
781
|
await this.assertInitialized();
|
|
752
782
|
if (message.recipient == null || message.recipient.trim() === '') {
|
|
@@ -758,6 +788,33 @@ class MessageBoxClient {
|
|
|
758
788
|
if (message.body == null || (typeof message.body === 'string' && message.body.trim().length === 0)) {
|
|
759
789
|
throw new Error('Every message must have a body!');
|
|
760
790
|
}
|
|
791
|
+
// Optional permission checking for backwards compatibility
|
|
792
|
+
let paymentData;
|
|
793
|
+
if (message.checkPermissions === true) {
|
|
794
|
+
try {
|
|
795
|
+
Logger.log('[MB CLIENT] Checking permissions and fees for message...');
|
|
796
|
+
// Get quote to check if payment is required
|
|
797
|
+
const quote = await this.getMessageBoxQuote({
|
|
798
|
+
recipient: message.recipient,
|
|
799
|
+
messageBox: message.messageBox
|
|
800
|
+
});
|
|
801
|
+
if (quote.recipientFee === -1) {
|
|
802
|
+
throw new Error('You have been blocked from sending messages to this recipient.');
|
|
803
|
+
}
|
|
804
|
+
if (quote.recipientFee > 0 || quote.deliveryFee > 0) {
|
|
805
|
+
const requiredPayment = quote.recipientFee + quote.deliveryFee;
|
|
806
|
+
if (requiredPayment > 0) {
|
|
807
|
+
Logger.log(`[MB CLIENT] Creating payment of ${requiredPayment} sats for message...`);
|
|
808
|
+
// Create payment using helper method
|
|
809
|
+
paymentData = await this.createMessagePayment(message.recipient, quote, overrideHost);
|
|
810
|
+
Logger.log('[MB CLIENT] Payment data prepared:', paymentData);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
catch (error) {
|
|
815
|
+
throw new Error(`Permission check failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
761
818
|
let messageId;
|
|
762
819
|
try {
|
|
763
820
|
const hmac = await this.walletClient.createHmac({
|
|
@@ -765,11 +822,11 @@ class MessageBoxClient {
|
|
|
765
822
|
protocolID: [1, 'messagebox'],
|
|
766
823
|
keyID: '1',
|
|
767
824
|
counterparty: message.recipient
|
|
768
|
-
});
|
|
825
|
+
}, originator);
|
|
769
826
|
messageId = (_a = message.messageId) !== null && _a !== void 0 ? _a : Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
770
827
|
}
|
|
771
828
|
catch (error) {
|
|
772
|
-
|
|
829
|
+
Logger.error('[MB CLIENT ERROR] Failed to generate HMAC:', error);
|
|
773
830
|
throw new Error('Failed to generate message identifier.');
|
|
774
831
|
}
|
|
775
832
|
let finalBody;
|
|
@@ -782,7 +839,7 @@ class MessageBoxClient {
|
|
|
782
839
|
keyID: '1',
|
|
783
840
|
counterparty: message.recipient,
|
|
784
841
|
plaintext: sdk_1.Utils.toArray(typeof message.body === 'string' ? message.body : JSON.stringify(message.body), 'utf8')
|
|
785
|
-
});
|
|
842
|
+
}, originator);
|
|
786
843
|
finalBody = JSON.stringify({ encryptedMessage: sdk_1.Utils.toBase64(encryptedMessage.ciphertext) });
|
|
787
844
|
}
|
|
788
845
|
const requestBody = {
|
|
@@ -790,20 +847,21 @@ class MessageBoxClient {
|
|
|
790
847
|
...message,
|
|
791
848
|
messageId,
|
|
792
849
|
body: finalBody
|
|
793
|
-
}
|
|
850
|
+
},
|
|
851
|
+
...(paymentData != null && { payment: paymentData })
|
|
794
852
|
};
|
|
795
853
|
try {
|
|
796
854
|
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : await this.resolveHostForRecipient(message.recipient);
|
|
797
|
-
|
|
798
|
-
|
|
855
|
+
Logger.log('[MB CLIENT] Sending HTTP request to:', `${finalHost}/sendMessage`);
|
|
856
|
+
Logger.log('[MB CLIENT] Request Body:', JSON.stringify(requestBody, null, 2));
|
|
799
857
|
if (this.myIdentityKey == null || this.myIdentityKey === '') {
|
|
800
858
|
try {
|
|
801
|
-
const keyResult = await this.walletClient.getPublicKey({ identityKey: true });
|
|
859
|
+
const keyResult = await this.walletClient.getPublicKey({ identityKey: true }, originator);
|
|
802
860
|
this.myIdentityKey = keyResult.publicKey;
|
|
803
|
-
|
|
861
|
+
Logger.log(`[MB CLIENT] Fetched identity key before sending request: ${this.myIdentityKey}`);
|
|
804
862
|
}
|
|
805
863
|
catch (error) {
|
|
806
|
-
|
|
864
|
+
Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
|
|
807
865
|
throw new Error('Identity key retrieval failed');
|
|
808
866
|
}
|
|
809
867
|
}
|
|
@@ -818,20 +876,20 @@ class MessageBoxClient {
|
|
|
818
876
|
throw new Error('[MB CLIENT ERROR] Response body has already been used!');
|
|
819
877
|
}
|
|
820
878
|
const parsedResponse = await response.json();
|
|
821
|
-
|
|
879
|
+
Logger.log('[MB CLIENT] Raw Response Body:', parsedResponse);
|
|
822
880
|
if (!response.ok) {
|
|
823
|
-
|
|
881
|
+
Logger.error(`[MB CLIENT ERROR] Failed to send message. HTTP ${response.status}: ${response.statusText}`);
|
|
824
882
|
throw new Error(`Message sending failed: HTTP ${response.status} - ${response.statusText}`);
|
|
825
883
|
}
|
|
826
884
|
if (parsedResponse.status !== 'success') {
|
|
827
|
-
|
|
885
|
+
Logger.error(`[MB CLIENT ERROR] Server returned an error: ${String(parsedResponse.description)}`);
|
|
828
886
|
throw new Error((_b = parsedResponse.description) !== null && _b !== void 0 ? _b : 'Unknown error from server.');
|
|
829
887
|
}
|
|
830
|
-
|
|
888
|
+
Logger.log('[MB CLIENT] Message successfully sent.');
|
|
831
889
|
return { ...parsedResponse, messageId };
|
|
832
890
|
}
|
|
833
891
|
catch (error) {
|
|
834
|
-
|
|
892
|
+
Logger.error('[MB CLIENT ERROR] Network or timeout error:', error);
|
|
835
893
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
836
894
|
throw new Error(`Failed to send message: ${errorMessage}`);
|
|
837
895
|
}
|
|
@@ -859,27 +917,27 @@ class MessageBoxClient {
|
|
|
859
917
|
* @example
|
|
860
918
|
* const { txid } = await client.anointHost('https://my-messagebox.io')
|
|
861
919
|
*/
|
|
862
|
-
async anointHost(host) {
|
|
863
|
-
|
|
920
|
+
async anointHost(host, originator) {
|
|
921
|
+
Logger.log('[MB CLIENT] Starting anointHost...');
|
|
864
922
|
try {
|
|
865
923
|
if (!host.startsWith('http')) {
|
|
866
924
|
throw new Error('Invalid host URL');
|
|
867
925
|
}
|
|
868
|
-
const identityKey = await this.getIdentityKey();
|
|
869
|
-
|
|
926
|
+
const identityKey = await this.getIdentityKey(originator);
|
|
927
|
+
Logger.log('[MB CLIENT] Fields - Identity:', identityKey, 'Host:', host);
|
|
870
928
|
const fields = [
|
|
871
929
|
sdk_1.Utils.toArray(identityKey, 'hex'),
|
|
872
930
|
sdk_1.Utils.toArray(host, 'utf8')
|
|
873
931
|
];
|
|
874
|
-
const pushdrop = new sdk_1.PushDrop(this.walletClient);
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
932
|
+
const pushdrop = new sdk_1.PushDrop(this.walletClient, originator);
|
|
933
|
+
Logger.log('Fields:', fields.map(a => sdk_1.Utils.toHex(a)));
|
|
934
|
+
Logger.log('ProtocolID:', [1, 'messagebox advertisement']);
|
|
935
|
+
Logger.log('KeyID:', '1');
|
|
936
|
+
Logger.log('SignAs:', 'self');
|
|
937
|
+
Logger.log('anyoneCanSpend:', false);
|
|
938
|
+
Logger.log('forSelf:', true);
|
|
881
939
|
const script = await pushdrop.lock(fields, [1, 'messagebox advertisement'], '1', 'anyone', true);
|
|
882
|
-
|
|
940
|
+
Logger.log('[MB CLIENT] PushDrop script:', script.toASM());
|
|
883
941
|
const { tx, txid } = await this.walletClient.createAction({
|
|
884
942
|
description: 'Anoint host for overlay routing',
|
|
885
943
|
outputs: [{
|
|
@@ -889,14 +947,14 @@ class MessageBoxClient {
|
|
|
889
947
|
outputDescription: 'Overlay advertisement output'
|
|
890
948
|
}],
|
|
891
949
|
options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
|
|
892
|
-
});
|
|
893
|
-
|
|
950
|
+
}, originator);
|
|
951
|
+
Logger.log('[MB CLIENT] Transaction created:', txid);
|
|
894
952
|
if (tx !== undefined) {
|
|
895
953
|
const broadcaster = new sdk_1.TopicBroadcaster(['tm_messagebox'], {
|
|
896
954
|
networkPreset: this.networkPreset
|
|
897
955
|
});
|
|
898
956
|
const result = await broadcaster.broadcast(sdk_1.Transaction.fromAtomicBEEF(tx));
|
|
899
|
-
|
|
957
|
+
Logger.log('[MB CLIENT] Advertisement broadcast succeeded. TXID:', result.txid);
|
|
900
958
|
if (typeof result.txid !== 'string') {
|
|
901
959
|
throw new Error('Anoint failed: broadcast did not return a txid');
|
|
902
960
|
}
|
|
@@ -905,7 +963,7 @@ class MessageBoxClient {
|
|
|
905
963
|
throw new Error('Anoint failed: failed to create action!');
|
|
906
964
|
}
|
|
907
965
|
catch (err) {
|
|
908
|
-
|
|
966
|
+
Logger.error('[MB CLIENT ERROR] anointHost threw:', err);
|
|
909
967
|
throw err;
|
|
910
968
|
}
|
|
911
969
|
}
|
|
@@ -913,6 +971,7 @@ class MessageBoxClient {
|
|
|
913
971
|
* @method revokeHostAdvertisement
|
|
914
972
|
* @async
|
|
915
973
|
* @param {AdvertisementToken} advertisementToken - The advertisement token containing the messagebox host to revoke.
|
|
974
|
+
* @param {string} [originator] - Optional originator to use with walletClient.
|
|
916
975
|
* @returns {Promise<{ txid: string }>} - The transaction ID of the revocation broadcast to the overlay network.
|
|
917
976
|
*
|
|
918
977
|
* @description
|
|
@@ -922,8 +981,8 @@ class MessageBoxClient {
|
|
|
922
981
|
* @example
|
|
923
982
|
* const { txid } = await client.revokeHost('https://my-messagebox.io')
|
|
924
983
|
*/
|
|
925
|
-
async revokeHostAdvertisement(advertisementToken) {
|
|
926
|
-
|
|
984
|
+
async revokeHostAdvertisement(advertisementToken, originator) {
|
|
985
|
+
Logger.log('[MB CLIENT] Starting revokeHost...');
|
|
927
986
|
const outpoint = `${advertisementToken.txid}.${advertisementToken.outputIndex}`;
|
|
928
987
|
try {
|
|
929
988
|
const { signableTransaction } = await this.walletClient.createAction({
|
|
@@ -936,13 +995,13 @@ class MessageBoxClient {
|
|
|
936
995
|
inputDescription: 'Revoking host advertisement token'
|
|
937
996
|
}
|
|
938
997
|
]
|
|
939
|
-
});
|
|
998
|
+
}, originator);
|
|
940
999
|
if (signableTransaction === undefined) {
|
|
941
1000
|
throw new Error('Failed to create signable transaction.');
|
|
942
1001
|
}
|
|
943
1002
|
const partialTx = sdk_1.Transaction.fromBEEF(signableTransaction.tx);
|
|
944
1003
|
// Prepare the unlocker
|
|
945
|
-
const pushdrop = new sdk_1.PushDrop(this.walletClient);
|
|
1004
|
+
const pushdrop = new sdk_1.PushDrop(this.walletClient, originator);
|
|
946
1005
|
const unlocker = await pushdrop.unlock([1, 'messagebox advertisement'], '1', 'anyone', 'all', false, advertisementToken.outputIndex, advertisementToken.lockingScript);
|
|
947
1006
|
// Convert to Transaction, apply signature
|
|
948
1007
|
const finalUnlockScript = await unlocker.sign(partialTx, advertisementToken.outputIndex);
|
|
@@ -957,7 +1016,7 @@ class MessageBoxClient {
|
|
|
957
1016
|
options: {
|
|
958
1017
|
acceptDelayedBroadcast: false
|
|
959
1018
|
}
|
|
960
|
-
});
|
|
1019
|
+
}, originator);
|
|
961
1020
|
if (signedTx === undefined) {
|
|
962
1021
|
throw new Error('Failed to finalize the transaction signature.');
|
|
963
1022
|
}
|
|
@@ -965,14 +1024,14 @@ class MessageBoxClient {
|
|
|
965
1024
|
networkPreset: this.networkPreset
|
|
966
1025
|
});
|
|
967
1026
|
const result = await broadcaster.broadcast(sdk_1.Transaction.fromAtomicBEEF(signedTx));
|
|
968
|
-
|
|
1027
|
+
Logger.log('[MB CLIENT] Revocation broadcast succeeded. TXID:', result.txid);
|
|
969
1028
|
if (typeof result.txid !== 'string') {
|
|
970
1029
|
throw new Error('Revoke failed: broadcast did not return a txid');
|
|
971
1030
|
}
|
|
972
1031
|
return { txid: result.txid };
|
|
973
1032
|
}
|
|
974
1033
|
catch (err) {
|
|
975
|
-
|
|
1034
|
+
Logger.error('[MB CLIENT ERROR] revokeHost threw:', err);
|
|
976
1035
|
throw err;
|
|
977
1036
|
}
|
|
978
1037
|
}
|
|
@@ -988,9 +1047,16 @@ class MessageBoxClient {
|
|
|
988
1047
|
*
|
|
989
1048
|
* Each message is:
|
|
990
1049
|
* - Parsed and, if encrypted, decrypted using AES-256-GCM via BRC-2-compliant ECDH key derivation and symmetric encryption.
|
|
1050
|
+
* - Automatically processed for payments: if the message includes recipient fee payments, they are internalized using `walletClient.internalizeAction()`.
|
|
991
1051
|
* - Returned as a normalized `PeerMessage` with readable string body content.
|
|
992
1052
|
*
|
|
993
|
-
*
|
|
1053
|
+
* Payment Processing:
|
|
1054
|
+
* - Detects messages that include payment data (from paid message delivery).
|
|
1055
|
+
* - Automatically internalizes recipient payment outputs, allowing you to receive payments without additional API calls.
|
|
1056
|
+
* - Only recipient payments are stored with messages - delivery fees are already processed by the server.
|
|
1057
|
+
* - Continues processing messages even if payment internalization fails.
|
|
1058
|
+
*
|
|
1059
|
+
* Decryption automatically derives a shared secret using the sender's identity key and the receiver's child private key.
|
|
994
1060
|
* If the sender is the same as the recipient, the `counterparty` is set to `'self'`.
|
|
995
1061
|
*
|
|
996
1062
|
* @throws {Error} If no messageBox is specified, the request fails, or the server returns an error.
|
|
@@ -998,22 +1064,24 @@ class MessageBoxClient {
|
|
|
998
1064
|
* @example
|
|
999
1065
|
* const messages = await client.listMessages({ messageBox: 'inbox' })
|
|
1000
1066
|
* messages.forEach(msg => console.log(msg.sender, msg.body))
|
|
1067
|
+
* // Payments included with messages are automatically received
|
|
1001
1068
|
*/
|
|
1002
|
-
async listMessages({ messageBox, host }) {
|
|
1069
|
+
async listMessages({ messageBox, host, originator }) {
|
|
1070
|
+
var _a;
|
|
1003
1071
|
await this.assertInitialized();
|
|
1004
1072
|
if (messageBox.trim() === '') {
|
|
1005
1073
|
throw new Error('MessageBox cannot be empty');
|
|
1006
1074
|
}
|
|
1007
1075
|
let hosts = host != null ? [host] : [];
|
|
1008
1076
|
if (hosts.length === 0) {
|
|
1009
|
-
const advertisedHosts = await this.queryAdvertisements(await this.getIdentityKey());
|
|
1077
|
+
const advertisedHosts = await this.queryAdvertisements(await this.getIdentityKey(originator), originator);
|
|
1010
1078
|
hosts = Array.from(new Set([this.host, ...advertisedHosts.map(h => h.host)]));
|
|
1011
1079
|
}
|
|
1012
1080
|
// Query each host in parallel
|
|
1013
1081
|
const fetchFromHost = async (host) => {
|
|
1014
1082
|
var _a;
|
|
1015
1083
|
try {
|
|
1016
|
-
|
|
1084
|
+
Logger.log(`[MB CLIENT] Listing messages from ${host}…`);
|
|
1017
1085
|
const res = await this.authFetch.fetch(`${host}/listMessages`, {
|
|
1018
1086
|
method: 'POST',
|
|
1019
1087
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1027,7 +1095,7 @@ class MessageBoxClient {
|
|
|
1027
1095
|
return data.messages;
|
|
1028
1096
|
}
|
|
1029
1097
|
catch (err) {
|
|
1030
|
-
|
|
1098
|
+
Logger.log(`[MB CLIENT DEBUG] listMessages failed for ${host}:`, err);
|
|
1031
1099
|
throw err; // re-throw to be caught in the settled promise
|
|
1032
1100
|
}
|
|
1033
1101
|
};
|
|
@@ -1070,25 +1138,69 @@ class MessageBoxClient {
|
|
|
1070
1138
|
for (const message of messages) {
|
|
1071
1139
|
try {
|
|
1072
1140
|
const parsedBody = typeof message.body === 'string' ? tryParse(message.body) : message.body;
|
|
1141
|
+
let messageContent = parsedBody;
|
|
1142
|
+
let paymentData;
|
|
1073
1143
|
if (parsedBody != null &&
|
|
1074
1144
|
typeof parsedBody === 'object' &&
|
|
1075
|
-
|
|
1076
|
-
|
|
1145
|
+
'message' in parsedBody) {
|
|
1146
|
+
// Handle wrapped message format (with payment data)
|
|
1147
|
+
const wrappedMessage = parsedBody.message;
|
|
1148
|
+
messageContent = typeof wrappedMessage === 'string'
|
|
1149
|
+
? tryParse(wrappedMessage)
|
|
1150
|
+
: wrappedMessage;
|
|
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: (_a = paymentData.description) !== null && _a !== void 0 ? _a : 'MessageBox recipient payment'
|
|
1166
|
+
}, originator);
|
|
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(
|
|
1082
|
-
});
|
|
1192
|
+
ciphertext: sdk_1.Utils.toArray(messageContent.encryptedMessage, 'base64')
|
|
1193
|
+
}, originator);
|
|
1083
1194
|
const decryptedText = sdk_1.Utils.toUTF8(decrypted.plaintext);
|
|
1084
1195
|
message.body = tryParse(decryptedText);
|
|
1085
1196
|
}
|
|
1086
1197
|
else {
|
|
1087
|
-
|
|
1198
|
+
// For non-encrypted messages, use the processed content
|
|
1199
|
+
message.body = messageContent !== null && messageContent !== void 0 ? messageContent : parsedBody;
|
|
1088
1200
|
}
|
|
1089
1201
|
}
|
|
1090
1202
|
catch (err) {
|
|
1091
|
-
|
|
1203
|
+
Logger.error('[MB CLIENT ERROR] Failed to parse or decrypt message in list:', err);
|
|
1092
1204
|
message.body = '[Error: Failed to decrypt or parse message]';
|
|
1093
1205
|
}
|
|
1094
1206
|
}
|
|
@@ -1117,18 +1229,18 @@ class MessageBoxClient {
|
|
|
1117
1229
|
* @example
|
|
1118
1230
|
* await client.acknowledgeMessage({ messageIds: ['msg123', 'msg456'] })
|
|
1119
1231
|
*/
|
|
1120
|
-
async acknowledgeMessage({ messageIds, host }) {
|
|
1232
|
+
async acknowledgeMessage({ messageIds, host, originator }) {
|
|
1121
1233
|
var _a;
|
|
1122
1234
|
await this.assertInitialized();
|
|
1123
1235
|
if (!Array.isArray(messageIds) || messageIds.length === 0) {
|
|
1124
1236
|
throw new Error('Message IDs array cannot be empty');
|
|
1125
1237
|
}
|
|
1126
|
-
|
|
1238
|
+
Logger.log(`[MB CLIENT] Acknowledging messages ${JSON.stringify(messageIds)}…`);
|
|
1127
1239
|
let hosts = host != null ? [host] : [];
|
|
1128
1240
|
if (hosts.length === 0) {
|
|
1129
1241
|
// 1. Determine all hosts (advertised + default)
|
|
1130
|
-
const identityKey = await this.getIdentityKey();
|
|
1131
|
-
const advertisedHosts = await this.queryAdvertisements(identityKey);
|
|
1242
|
+
const identityKey = await this.getIdentityKey(originator);
|
|
1243
|
+
const advertisedHosts = await this.queryAdvertisements(identityKey, undefined, originator);
|
|
1132
1244
|
hosts = Array.from(new Set([this.host, ...advertisedHosts.map(h => h.host)]));
|
|
1133
1245
|
}
|
|
1134
1246
|
// 2. Dispatch parallel acknowledge requests
|
|
@@ -1144,11 +1256,11 @@ class MessageBoxClient {
|
|
|
1144
1256
|
const data = await res.json();
|
|
1145
1257
|
if (data.status === 'error')
|
|
1146
1258
|
throw new Error(data.description);
|
|
1147
|
-
|
|
1259
|
+
Logger.log(`[MB CLIENT] Acknowledged on ${host}`);
|
|
1148
1260
|
return data.status;
|
|
1149
1261
|
}
|
|
1150
1262
|
catch (err) {
|
|
1151
|
-
|
|
1263
|
+
Logger.warn(`[MB CLIENT WARN] acknowledgeMessage failed for ${host}:`, err);
|
|
1152
1264
|
return null;
|
|
1153
1265
|
}
|
|
1154
1266
|
};
|
|
@@ -1166,6 +1278,512 @@ class MessageBoxClient {
|
|
|
1166
1278
|
}
|
|
1167
1279
|
throw new Error(`Failed to acknowledge messages on all hosts: ${errs.map(e => String(e)).join('; ')}`);
|
|
1168
1280
|
}
|
|
1281
|
+
// ===========================
|
|
1282
|
+
// PERMISSION MANAGEMENT METHODS
|
|
1283
|
+
// ===========================
|
|
1284
|
+
/**
|
|
1285
|
+
* @method setMessageBoxPermission
|
|
1286
|
+
* @async
|
|
1287
|
+
* @param {SetMessageBoxPermissionParams} params - Permission configuration
|
|
1288
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1289
|
+
* @returns {Promise<void>} Permission status after setting
|
|
1290
|
+
*
|
|
1291
|
+
* @description
|
|
1292
|
+
* Sets permission for receiving messages in a specific messageBox.
|
|
1293
|
+
* Can set sender-specific permissions or box-wide defaults.
|
|
1294
|
+
*
|
|
1295
|
+
* @example
|
|
1296
|
+
* // Set box-wide default: allow notifications for 10 sats
|
|
1297
|
+
* await client.setMessageBoxPermission({ messageBox: 'notifications', recipientFee: 10 })
|
|
1298
|
+
*
|
|
1299
|
+
* // Block specific sender
|
|
1300
|
+
* await client.setMessageBoxPermission({
|
|
1301
|
+
* messageBox: 'notifications',
|
|
1302
|
+
* sender: '03abc123...',
|
|
1303
|
+
* recipientFee: -1
|
|
1304
|
+
* })
|
|
1305
|
+
*/
|
|
1306
|
+
async setMessageBoxPermission(params, overrideHost) {
|
|
1307
|
+
await this.assertInitialized();
|
|
1308
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
|
|
1309
|
+
Logger.log('[MB CLIENT] Setting messageBox permission...');
|
|
1310
|
+
const response = await this.authFetch.fetch(`${finalHost}/permissions/set`, {
|
|
1311
|
+
method: 'POST',
|
|
1312
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1313
|
+
body: JSON.stringify({
|
|
1314
|
+
messageBox: params.messageBox,
|
|
1315
|
+
recipientFee: params.recipientFee,
|
|
1316
|
+
...(params.sender != null && { sender: params.sender })
|
|
1317
|
+
})
|
|
1318
|
+
});
|
|
1319
|
+
if (!response.ok) {
|
|
1320
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1321
|
+
throw new Error(`Failed to set permission: HTTP ${response.status} - ${String(errorData.description) !== '' ? String(errorData.description) : response.statusText}`);
|
|
1322
|
+
}
|
|
1323
|
+
const { status, description } = await response.json();
|
|
1324
|
+
if (status === 'error') {
|
|
1325
|
+
throw new Error(description !== null && description !== void 0 ? description : 'Failed to set permission');
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* @method getMessageBoxPermission
|
|
1330
|
+
* @async
|
|
1331
|
+
* @param {GetMessageBoxPermissionParams} params - Permission query parameters
|
|
1332
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1333
|
+
* @returns {Promise<MessageBoxPermission | null>} Permission data (null if not set)
|
|
1334
|
+
*
|
|
1335
|
+
* @description
|
|
1336
|
+
* Gets current permission data for a sender/messageBox combination.
|
|
1337
|
+
* Returns null if no permission is set.
|
|
1338
|
+
*
|
|
1339
|
+
* @example
|
|
1340
|
+
* const status = await client.getMessageBoxPermission({
|
|
1341
|
+
* recipient: '03def456...',
|
|
1342
|
+
* messageBox: 'notifications',
|
|
1343
|
+
* sender: '03abc123...'
|
|
1344
|
+
* })
|
|
1345
|
+
*/
|
|
1346
|
+
async getMessageBoxPermission(params, overrideHost) {
|
|
1347
|
+
var _a;
|
|
1348
|
+
await this.assertInitialized();
|
|
1349
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : await this.resolveHostForRecipient(params.recipient);
|
|
1350
|
+
const queryParams = new URLSearchParams({
|
|
1351
|
+
recipient: params.recipient,
|
|
1352
|
+
messageBox: params.messageBox,
|
|
1353
|
+
...(params.sender != null && { sender: params.sender })
|
|
1354
|
+
});
|
|
1355
|
+
Logger.log('[MB CLIENT] Getting messageBox permission...');
|
|
1356
|
+
const response = await this.authFetch.fetch(`${finalHost}/permissions/get?${queryParams.toString()}`, {
|
|
1357
|
+
method: 'GET'
|
|
1358
|
+
});
|
|
1359
|
+
if (!response.ok) {
|
|
1360
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1361
|
+
throw new Error(`Failed to get permission: HTTP ${response.status} - ${String(errorData.description) !== '' ? String(errorData.description) : response.statusText}`);
|
|
1362
|
+
}
|
|
1363
|
+
const data = await response.json();
|
|
1364
|
+
if (data.status === 'error') {
|
|
1365
|
+
throw new Error((_a = data.description) !== null && _a !== void 0 ? _a : 'Failed to get permission');
|
|
1366
|
+
}
|
|
1367
|
+
return data.permission;
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* @method getMessageBoxQuote
|
|
1371
|
+
* @async
|
|
1372
|
+
* @param {GetQuoteParams} params - Quote request parameters
|
|
1373
|
+
* @returns {Promise<MessageBoxQuote>} Fee quote and permission status
|
|
1374
|
+
*
|
|
1375
|
+
* @description
|
|
1376
|
+
* Gets a fee quote for sending a message, including delivery and recipient fees.
|
|
1377
|
+
*
|
|
1378
|
+
* @example
|
|
1379
|
+
* const quote = await client.getMessageBoxQuote({
|
|
1380
|
+
* recipient: '03def456...',
|
|
1381
|
+
* messageBox: 'notifications'
|
|
1382
|
+
* })
|
|
1383
|
+
*/
|
|
1384
|
+
async getMessageBoxQuote(params, overrideHost) {
|
|
1385
|
+
var _a;
|
|
1386
|
+
await this.assertInitialized();
|
|
1387
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : await this.resolveHostForRecipient(params.recipient);
|
|
1388
|
+
const queryParams = new URLSearchParams({
|
|
1389
|
+
recipient: params.recipient,
|
|
1390
|
+
messageBox: params.messageBox
|
|
1391
|
+
});
|
|
1392
|
+
Logger.log('[MB CLIENT] Getting messageBox quote...');
|
|
1393
|
+
const response = await this.authFetch.fetch(`${finalHost}/permissions/quote?${queryParams.toString()}`, {
|
|
1394
|
+
method: 'GET'
|
|
1395
|
+
});
|
|
1396
|
+
if (!response.ok) {
|
|
1397
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1398
|
+
throw new Error(`Failed to get quote: HTTP ${response.status} - ${(_a = String(errorData.description)) !== null && _a !== void 0 ? _a : response.statusText}`);
|
|
1399
|
+
}
|
|
1400
|
+
const { status, description, quote } = await response.json();
|
|
1401
|
+
if (status === 'error') {
|
|
1402
|
+
throw new Error(description !== null && description !== void 0 ? description : 'Failed to get quote');
|
|
1403
|
+
}
|
|
1404
|
+
const deliveryAgentIdentityKey = response.headers.get('x-bsv-auth-identity-key');
|
|
1405
|
+
if (deliveryAgentIdentityKey == null) {
|
|
1406
|
+
throw new Error('Failed to get quote: Delivery agent did not provide their identity key');
|
|
1407
|
+
}
|
|
1408
|
+
return {
|
|
1409
|
+
recipientFee: quote.recipientFee,
|
|
1410
|
+
deliveryFee: quote.deliveryFee,
|
|
1411
|
+
deliveryAgentIdentityKey
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* @method listMessageBoxPermissions
|
|
1416
|
+
* @async
|
|
1417
|
+
* @param {ListPermissionsParams} [params] - Optional filtering and pagination parameters
|
|
1418
|
+
* @returns {Promise<MessageBoxPermission[]>} List of current permissions
|
|
1419
|
+
*
|
|
1420
|
+
* @description
|
|
1421
|
+
* Lists permissions for the authenticated user's messageBoxes with optional pagination.
|
|
1422
|
+
*
|
|
1423
|
+
* @example
|
|
1424
|
+
* // List all permissions
|
|
1425
|
+
* const all = await client.listMessageBoxPermissions()
|
|
1426
|
+
*
|
|
1427
|
+
* // List only notification permissions with pagination
|
|
1428
|
+
* const notifications = await client.listMessageBoxPermissions({
|
|
1429
|
+
* messageBox: 'notifications',
|
|
1430
|
+
* limit: 50,
|
|
1431
|
+
* offset: 0
|
|
1432
|
+
* })
|
|
1433
|
+
*/
|
|
1434
|
+
async listMessageBoxPermissions(params, overrideHost) {
|
|
1435
|
+
var _a;
|
|
1436
|
+
await this.assertInitialized();
|
|
1437
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
|
|
1438
|
+
const queryParams = new URLSearchParams();
|
|
1439
|
+
if ((params === null || params === void 0 ? void 0 : params.messageBox) != null) {
|
|
1440
|
+
queryParams.set('message_box', params.messageBox);
|
|
1441
|
+
}
|
|
1442
|
+
if ((params === null || params === void 0 ? void 0 : params.limit) !== undefined) {
|
|
1443
|
+
queryParams.set('limit', params.limit.toString());
|
|
1444
|
+
}
|
|
1445
|
+
if ((params === null || params === void 0 ? void 0 : params.offset) !== undefined) {
|
|
1446
|
+
queryParams.set('offset', params.offset.toString());
|
|
1447
|
+
}
|
|
1448
|
+
Logger.log('[MB CLIENT] Listing messageBox permissions with params:', queryParams.toString());
|
|
1449
|
+
const response = await this.authFetch.fetch(`${finalHost}/permissions/list?${queryParams.toString()}`, {
|
|
1450
|
+
method: 'GET'
|
|
1451
|
+
});
|
|
1452
|
+
if (!response.ok) {
|
|
1453
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1454
|
+
throw new Error(`Failed to list permissions: HTTP ${response.status} - ${String(errorData.description) !== '' ? String(errorData.description) : response.statusText}`);
|
|
1455
|
+
}
|
|
1456
|
+
const data = await response.json();
|
|
1457
|
+
if (data.status === 'error') {
|
|
1458
|
+
throw new Error((_a = data.description) !== null && _a !== void 0 ? _a : 'Failed to list permissions');
|
|
1459
|
+
}
|
|
1460
|
+
return data.permissions.map((p) => ({
|
|
1461
|
+
sender: p.sender,
|
|
1462
|
+
messageBox: p.message_box,
|
|
1463
|
+
recipientFee: p.recipient_fee,
|
|
1464
|
+
status: MessageBoxClient.getStatusFromFee(p.recipient_fee),
|
|
1465
|
+
createdAt: p.created_at,
|
|
1466
|
+
updatedAt: p.updated_at
|
|
1467
|
+
}));
|
|
1468
|
+
}
|
|
1469
|
+
// ===========================
|
|
1470
|
+
// NOTIFICATION CONVENIENCE METHODS
|
|
1471
|
+
// ===========================
|
|
1472
|
+
/**
|
|
1473
|
+
* @method allowNotificationsFromPeer
|
|
1474
|
+
* @async
|
|
1475
|
+
* @param {PubKeyHex} identityKey - Sender's identity key to allow
|
|
1476
|
+
* @param {number} [recipientFee=0] - Fee to charge (0 for always allow)
|
|
1477
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1478
|
+
* @returns {Promise<void>} Permission status after allowing
|
|
1479
|
+
*
|
|
1480
|
+
* @description
|
|
1481
|
+
* Convenience method to allow notifications from a specific peer.
|
|
1482
|
+
*
|
|
1483
|
+
* @example
|
|
1484
|
+
* await client.allowNotificationsFromPeer('03abc123...') // Always allow
|
|
1485
|
+
* await client.allowNotificationsFromPeer('03def456...', 5) // Allow for 5 sats
|
|
1486
|
+
*/
|
|
1487
|
+
async allowNotificationsFromPeer(identityKey, recipientFee = 0, overrideHost) {
|
|
1488
|
+
await this.setMessageBoxPermission({
|
|
1489
|
+
messageBox: 'notifications',
|
|
1490
|
+
sender: identityKey,
|
|
1491
|
+
recipientFee
|
|
1492
|
+
}, overrideHost);
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* @method denyNotificationsFromPeer
|
|
1496
|
+
* @async
|
|
1497
|
+
* @param {PubKeyHex} identityKey - Sender's identity key to block
|
|
1498
|
+
* @returns {Promise<void>} Permission status after denying
|
|
1499
|
+
*
|
|
1500
|
+
* @description
|
|
1501
|
+
* Convenience method to block notifications from a specific peer.
|
|
1502
|
+
*
|
|
1503
|
+
* @example
|
|
1504
|
+
* await client.denyNotificationsFromPeer('03spam123...')
|
|
1505
|
+
*/
|
|
1506
|
+
async denyNotificationsFromPeer(identityKey, overrideHost) {
|
|
1507
|
+
await this.setMessageBoxPermission({
|
|
1508
|
+
messageBox: 'notifications',
|
|
1509
|
+
sender: identityKey,
|
|
1510
|
+
recipientFee: -1
|
|
1511
|
+
}, overrideHost);
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* @method checkPeerNotificationStatus
|
|
1515
|
+
* @async
|
|
1516
|
+
* @param {PubKeyHex} identityKey - Sender's identity key to check
|
|
1517
|
+
* @returns {Promise<MessageBoxPermission>} Current permission status
|
|
1518
|
+
*
|
|
1519
|
+
* @description
|
|
1520
|
+
* Convenience method to check notification permission for a specific peer.
|
|
1521
|
+
*
|
|
1522
|
+
* @example
|
|
1523
|
+
* const status = await client.checkPeerNotificationStatus('03abc123...')
|
|
1524
|
+
* console.log(status.allowed) // true/false
|
|
1525
|
+
*/
|
|
1526
|
+
async checkPeerNotificationStatus(identityKey, overrideHost) {
|
|
1527
|
+
const myIdentityKey = await this.getIdentityKey();
|
|
1528
|
+
return await this.getMessageBoxPermission({
|
|
1529
|
+
recipient: myIdentityKey,
|
|
1530
|
+
messageBox: 'notifications',
|
|
1531
|
+
sender: identityKey
|
|
1532
|
+
}, overrideHost);
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* @method listPeerNotifications
|
|
1536
|
+
* @async
|
|
1537
|
+
* @returns {Promise<MessageBoxPermission[]>} List of notification permissions
|
|
1538
|
+
*
|
|
1539
|
+
* @description
|
|
1540
|
+
* Convenience method to list all notification permissions.
|
|
1541
|
+
*
|
|
1542
|
+
* @example
|
|
1543
|
+
* const notifications = await client.listPeerNotifications()
|
|
1544
|
+
*/
|
|
1545
|
+
async listPeerNotifications(overrideHost) {
|
|
1546
|
+
return await this.listMessageBoxPermissions({ messageBox: 'notifications' }, overrideHost);
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* @method sendNotification
|
|
1550
|
+
* @async
|
|
1551
|
+
* @param {PubKeyHex} recipient - Recipient's identity key
|
|
1552
|
+
* @param {string | object} body - Notification content
|
|
1553
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1554
|
+
* @returns {Promise<SendMessageResponse>} Send result
|
|
1555
|
+
*
|
|
1556
|
+
* @description
|
|
1557
|
+
* Convenience method to send a notification with automatic quote fetching and payment handling.
|
|
1558
|
+
* Automatically determines the required payment amount and creates the payment if needed.
|
|
1559
|
+
*
|
|
1560
|
+
* @example
|
|
1561
|
+
* // Send notification (auto-determines payment needed)
|
|
1562
|
+
* await client.sendNotification('03def456...', 'Hello!')
|
|
1563
|
+
*
|
|
1564
|
+
* // Send with maximum payment limit for safety
|
|
1565
|
+
* await client.sendNotification('03def456...', { title: 'Alert', body: 'Important update' }, 50)
|
|
1566
|
+
*/
|
|
1567
|
+
async sendNotification(recipient, body, overrideHost) {
|
|
1568
|
+
await this.assertInitialized();
|
|
1569
|
+
// Use sendMessage with permission checking enabled
|
|
1570
|
+
// This eliminates duplication of quote fetching and payment logic
|
|
1571
|
+
return await this.sendMessage({
|
|
1572
|
+
recipient,
|
|
1573
|
+
messageBox: 'notifications',
|
|
1574
|
+
body,
|
|
1575
|
+
checkPermissions: true
|
|
1576
|
+
}, overrideHost);
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Register a device for FCM push notifications.
|
|
1580
|
+
*
|
|
1581
|
+
* @async
|
|
1582
|
+
* @param {DeviceRegistrationParams} params - Device registration parameters
|
|
1583
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1584
|
+
* @returns {Promise<DeviceRegistrationResponse>} Registration response
|
|
1585
|
+
*
|
|
1586
|
+
* @description
|
|
1587
|
+
* Registers a device with the message box server to receive FCM push notifications.
|
|
1588
|
+
* The FCM token is obtained from Firebase SDK on the client side.
|
|
1589
|
+
*
|
|
1590
|
+
* @example
|
|
1591
|
+
* const result = await client.registerDevice({
|
|
1592
|
+
* fcmToken: 'eBo8F...',
|
|
1593
|
+
* platform: 'ios',
|
|
1594
|
+
* deviceId: 'iPhone15Pro'
|
|
1595
|
+
* })
|
|
1596
|
+
*/
|
|
1597
|
+
async registerDevice(params, overrideHost) {
|
|
1598
|
+
var _a, _b, _c, _d, _e;
|
|
1599
|
+
await this.assertInitialized();
|
|
1600
|
+
if (params.fcmToken == null || params.fcmToken.trim() === '') {
|
|
1601
|
+
throw new Error('fcmToken is required and must be a non-empty string');
|
|
1602
|
+
}
|
|
1603
|
+
// Validate platform if provided
|
|
1604
|
+
const validPlatforms = ['ios', 'android', 'web'];
|
|
1605
|
+
if (params.platform != null && !validPlatforms.includes(params.platform)) {
|
|
1606
|
+
throw new Error('platform must be one of: ios, android, web');
|
|
1607
|
+
}
|
|
1608
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
|
|
1609
|
+
Logger.log('[MB CLIENT] Registering device for FCM notifications...');
|
|
1610
|
+
const response = await this.authFetch.fetch(`${finalHost}/registerDevice`, {
|
|
1611
|
+
method: 'POST',
|
|
1612
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1613
|
+
body: JSON.stringify({
|
|
1614
|
+
fcmToken: params.fcmToken.trim(),
|
|
1615
|
+
deviceId: (_b = (_a = params.deviceId) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : undefined,
|
|
1616
|
+
platform: (_c = params.platform) !== null && _c !== void 0 ? _c : undefined
|
|
1617
|
+
})
|
|
1618
|
+
});
|
|
1619
|
+
if (!response.ok) {
|
|
1620
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1621
|
+
const description = (_d = String(errorData.description)) !== null && _d !== void 0 ? _d : response.statusText;
|
|
1622
|
+
throw new Error(`Failed to register device: HTTP ${response.status} - ${description}`);
|
|
1623
|
+
}
|
|
1624
|
+
const data = await response.json();
|
|
1625
|
+
if (data.status === 'error') {
|
|
1626
|
+
throw new Error((_e = data.description) !== null && _e !== void 0 ? _e : 'Failed to register device');
|
|
1627
|
+
}
|
|
1628
|
+
Logger.log('[MB CLIENT] Device registered successfully');
|
|
1629
|
+
return {
|
|
1630
|
+
status: data.status,
|
|
1631
|
+
message: data.message,
|
|
1632
|
+
deviceId: data.deviceId
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* List all registered devices for push notifications.
|
|
1637
|
+
*
|
|
1638
|
+
* @async
|
|
1639
|
+
* @param {string} [overrideHost] - Optional host override
|
|
1640
|
+
* @returns {Promise<RegisteredDevice[]>} Array of registered devices
|
|
1641
|
+
*
|
|
1642
|
+
* @description
|
|
1643
|
+
* Retrieves all devices registered by the authenticated user for FCM push notifications.
|
|
1644
|
+
* Only shows devices belonging to the current user (authenticated via AuthFetch).
|
|
1645
|
+
*
|
|
1646
|
+
* @example
|
|
1647
|
+
* const devices = await client.listRegisteredDevices()
|
|
1648
|
+
* console.log(`Found ${devices.length} registered devices`)
|
|
1649
|
+
* devices.forEach(device => {
|
|
1650
|
+
* console.log(`Device: ${device.platform} - ${device.fcmToken}`)
|
|
1651
|
+
* })
|
|
1652
|
+
*/
|
|
1653
|
+
async listRegisteredDevices(overrideHost) {
|
|
1654
|
+
var _a, _b;
|
|
1655
|
+
await this.assertInitialized();
|
|
1656
|
+
const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
|
|
1657
|
+
Logger.log('[MB CLIENT] Listing registered devices...');
|
|
1658
|
+
const response = await this.authFetch.fetch(`${finalHost}/devices`, {
|
|
1659
|
+
method: 'GET'
|
|
1660
|
+
});
|
|
1661
|
+
if (!response.ok) {
|
|
1662
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1663
|
+
const description = (_a = String(errorData.description)) !== null && _a !== void 0 ? _a : response.statusText;
|
|
1664
|
+
throw new Error(`Failed to list devices: HTTP ${response.status} - ${description}`);
|
|
1665
|
+
}
|
|
1666
|
+
const data = await response.json();
|
|
1667
|
+
if (data.status === 'error') {
|
|
1668
|
+
throw new Error((_b = data.description) !== null && _b !== void 0 ? _b : 'Failed to list devices');
|
|
1669
|
+
}
|
|
1670
|
+
Logger.log(`[MB CLIENT] Found ${data.devices.length} registered devices`);
|
|
1671
|
+
return data.devices;
|
|
1672
|
+
}
|
|
1673
|
+
// ===========================
|
|
1674
|
+
// PRIVATE HELPER METHODS
|
|
1675
|
+
// ===========================
|
|
1676
|
+
static getStatusFromFee(fee) {
|
|
1677
|
+
if (fee === -1)
|
|
1678
|
+
return 'blocked';
|
|
1679
|
+
if (fee === 0)
|
|
1680
|
+
return 'always_allow';
|
|
1681
|
+
return 'payment_required';
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Creates payment transaction for message delivery fees
|
|
1685
|
+
* TODO: Consider consolidating payment generating logic with a util PeerPayClient can use as well.
|
|
1686
|
+
* @private
|
|
1687
|
+
* @param {string} recipient - Recipient identity key
|
|
1688
|
+
* @param {MessageBoxQuote} quote - Fee quote with delivery and recipient fees
|
|
1689
|
+
* @param {string} description - Description for the payment transaction
|
|
1690
|
+
* @returns {Promise<Payment>} Payment transaction data
|
|
1691
|
+
*/
|
|
1692
|
+
async createMessagePayment(recipient, quote, description = 'MessageBox delivery payment', originator) {
|
|
1693
|
+
if (quote.recipientFee <= 0 && quote.deliveryFee <= 0) {
|
|
1694
|
+
throw new Error('No payment required');
|
|
1695
|
+
}
|
|
1696
|
+
Logger.log(`[MB CLIENT] Creating payment transaction for ${quote.recipientFee} sats (delivery: ${quote.deliveryFee}, recipient: ${quote.recipientFee})`);
|
|
1697
|
+
const outputs = [];
|
|
1698
|
+
const createActionOutputs = [];
|
|
1699
|
+
// Get sender identity key for remittance data
|
|
1700
|
+
const senderIdentityKey = await this.getIdentityKey();
|
|
1701
|
+
// Add server delivery fee output if > 0
|
|
1702
|
+
let outputIndex = 0;
|
|
1703
|
+
if (quote.deliveryFee > 0) {
|
|
1704
|
+
const derivationPrefix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
|
|
1705
|
+
const derivationSuffix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
|
|
1706
|
+
// Get host's derived public key
|
|
1707
|
+
const { publicKey: derivedKeyResult } = await this.walletClient.getPublicKey({
|
|
1708
|
+
protocolID: [2, '3241645161d8'],
|
|
1709
|
+
keyID: `${derivationPrefix} ${derivationSuffix}`,
|
|
1710
|
+
counterparty: quote.deliveryAgentIdentityKey
|
|
1711
|
+
}, originator);
|
|
1712
|
+
// Create locking script using host's public key
|
|
1713
|
+
const lockingScript = new sdk_1.P2PKH().lock(sdk_1.PublicKey.fromString(derivedKeyResult).toAddress()).toHex();
|
|
1714
|
+
// Add to createAction outputs
|
|
1715
|
+
createActionOutputs.push({
|
|
1716
|
+
satoshis: quote.deliveryFee,
|
|
1717
|
+
lockingScript,
|
|
1718
|
+
outputDescription: 'MessageBox server delivery fee',
|
|
1719
|
+
customInstructions: JSON.stringify({
|
|
1720
|
+
derivationPrefix,
|
|
1721
|
+
derivationSuffix,
|
|
1722
|
+
recipientIdentityKey: quote.deliveryAgentIdentityKey
|
|
1723
|
+
})
|
|
1724
|
+
});
|
|
1725
|
+
outputs.push({
|
|
1726
|
+
outputIndex: outputIndex++,
|
|
1727
|
+
protocol: 'wallet payment',
|
|
1728
|
+
paymentRemittance: {
|
|
1729
|
+
derivationPrefix,
|
|
1730
|
+
derivationSuffix,
|
|
1731
|
+
senderIdentityKey
|
|
1732
|
+
}
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
// Add recipient fee output if > 0
|
|
1736
|
+
if (quote.recipientFee > 0) {
|
|
1737
|
+
const derivationPrefix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
|
|
1738
|
+
const derivationSuffix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
|
|
1739
|
+
// Get a derived public key for the recipient that "anyone" can verify
|
|
1740
|
+
const anyoneWallet = new sdk_1.ProtoWallet('anyone');
|
|
1741
|
+
const { publicKey: derivedKeyResult } = await anyoneWallet.getPublicKey({
|
|
1742
|
+
protocolID: [2, '3241645161d8'],
|
|
1743
|
+
keyID: `${derivationPrefix} ${derivationSuffix}`,
|
|
1744
|
+
counterparty: recipient
|
|
1745
|
+
});
|
|
1746
|
+
if (derivedKeyResult == null || derivedKeyResult.trim() === '') {
|
|
1747
|
+
throw new Error('Failed to derive recipient\'s public key');
|
|
1748
|
+
}
|
|
1749
|
+
// Create locking script using recipient's public key
|
|
1750
|
+
const lockingScript = new sdk_1.P2PKH().lock(sdk_1.PublicKey.fromString(derivedKeyResult).toAddress()).toHex();
|
|
1751
|
+
// Add to createAction outputs
|
|
1752
|
+
createActionOutputs.push({
|
|
1753
|
+
satoshis: quote.recipientFee,
|
|
1754
|
+
lockingScript,
|
|
1755
|
+
outputDescription: 'Recipient message fee',
|
|
1756
|
+
customInstructions: JSON.stringify({
|
|
1757
|
+
derivationPrefix,
|
|
1758
|
+
derivationSuffix,
|
|
1759
|
+
recipientIdentityKey: recipient
|
|
1760
|
+
})
|
|
1761
|
+
});
|
|
1762
|
+
outputs.push({
|
|
1763
|
+
outputIndex: outputIndex++,
|
|
1764
|
+
protocol: 'wallet payment',
|
|
1765
|
+
paymentRemittance: {
|
|
1766
|
+
derivationPrefix,
|
|
1767
|
+
derivationSuffix,
|
|
1768
|
+
senderIdentityKey: (await anyoneWallet.getPublicKey({ identityKey: true })).publicKey
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
const { tx } = await this.walletClient.createAction({
|
|
1773
|
+
description,
|
|
1774
|
+
outputs: createActionOutputs,
|
|
1775
|
+
options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
|
|
1776
|
+
}, originator);
|
|
1777
|
+
if (tx == null) {
|
|
1778
|
+
throw new Error('Failed to create payment transaction');
|
|
1779
|
+
}
|
|
1780
|
+
return {
|
|
1781
|
+
tx,
|
|
1782
|
+
outputs,
|
|
1783
|
+
description
|
|
1784
|
+
// labels
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1169
1787
|
}
|
|
1170
1788
|
exports.MessageBoxClient = MessageBoxClient;
|
|
1171
1789
|
//# sourceMappingURL=MessageBoxClient.js.map
|