diff --git a/docs/packages/messaging/message-box-client.md b/docs/packages/messaging/message-box-client.md index 412b48b35..f11ac0d84 100644 --- a/docs/packages/messaging/message-box-client.md +++ b/docs/packages/messaging/message-box-client.md @@ -3,10 +3,10 @@ id: pkg-message-box-client title: '@bsv/message-box-client' kind: package domain: messaging -version: '2.4.1' +version: '2.5.0' source_repo: 'bsv-blockchain/ts-stack' -last_updated: '2026-08-14' -last_verified: '2026-08-14' +last_updated: '2026-08-24' +last_verified: '2026-08-24' review_cadence_days: 30 npm: 'https://www.npmjs.com/package/@bsv/message-box-client' repo: 'https://github.com/bsv-blockchain/ts-stack/tree/main/packages/messaging/message-box-client' @@ -61,7 +61,8 @@ is useful. ## What it provides -- `MessageBoxClient` — authenticated HTTP polling and live WebSocket delivery +- `MessageBoxClient` — authenticated HTTP polling and live WebSocket delivery, + with selectable socket transports - encryption through the BRC-100 wallet protocol, enabled by default - overlay host advertisement and public-HTTPS discovery - sender-specific and box-wide permissions with fee quotes diff --git a/governance/package-release-notes.json b/governance/package-release-notes.json index 393ed8dea..b99ab6394 100644 --- a/governance/package-release-notes.json +++ b/governance/package-release-notes.json @@ -97,9 +97,9 @@ { "name": "@bsv/message-box-client", "publishedVersion": "2.4.0", - "releaseType": "patch", - "summary": "Preserves BRC-29 payments, paid messages, remittances, and peer tokens across binary Wallet Wire results and historical numeric-key JSON payloads, with strict rejection of sparse or invalid byte records.", - "migration": "No API migration is required. Upgrade @bsv/sdk and @bsv/message-box-client together; historical number-array wallets, current Uint8Array substrates, and already-pending numeric-key messages interoperate through the same portable transaction form." + "releaseType": "minor", + "summary": "Adds an optional socketOptions client option that forwards the AuthSocketClient options other than wallet and originator, letting callers select Socket.IO transports such as websocket-only against deployments that do not carry Engine.IO HTTP polling, and reach certificate requests, session management, auth-message concurrency, and error reporting. Also preserves BRC-29 payments, paid messages, remittances, and peer tokens across binary Wallet Wire results and historical numeric-key JSON payloads, with strict rejection of sparse or invalid byte records.", + "migration": "No API migration is required. socketOptions is optional and nothing is forwarded by default, so socket transport negotiation and every HTTP code path are unchanged for existing callers; wallet and originator stay client-owned and cannot be overridden through it. Upgrade @bsv/sdk and @bsv/message-box-client together; historical number-array wallets, current Uint8Array substrates, and already-pending numeric-key messages interoperate through the same portable transaction form." }, { "name": "@bsv/overlay", diff --git a/governance/repository-health/baselines.json b/governance/repository-health/baselines.json index dedb65461..7b27c4635 100644 --- a/governance/repository-health/baselines.json +++ b/governance/repository-health/baselines.json @@ -308,7 +308,7 @@ "@bsv/templates": "1.10.0", "@bsv/authsocket": "2.1.6", "@bsv/authsocket-client": "2.1.5", - "@bsv/message-box-client": "2.4.1", + "@bsv/message-box-client": "2.5.0", "@bsv/paymail": "2.4.6", "@bsv/402-pay": "0.2.4", "@bsv/auth": "0.1.3", diff --git a/packages/messaging/message-box-client/CHANGELOG.md b/packages/messaging/message-box-client/CHANGELOG.md index ddf8763fb..344bfdac8 100644 --- a/packages/messaging/message-box-client/CHANGELOG.md +++ b/packages/messaging/message-box-client/CHANGELOG.md @@ -15,6 +15,16 @@ All notable changes to this project will be documented in this file. The format ### Added +- Added an optional `socketOptions` client option, forwarded to + `AuthSocketClient` when the live socket is created. It carries the + `AuthSocketClient` options other than `wallet` and `originator`, which the + client owns, so callers can select Socket.IO transports (for example + `{ managerOptions: { transports: ['websocket'] } }`) to reach deployments that + do not carry Engine.IO's HTTP polling transport, and can also supply + `requestedCertificates`, `sessionManager`, `maxPendingAuthMessages`, and + `onError`. Nothing is forwarded when unset, so default transport negotiation + and all HTTP code paths are unchanged. + - Added the `teratestnet` overlay preset. TTN clients must provide an explicit Message Box host until a dedicated TTN deployment is available, preventing accidental use of the existing testnet staging service. diff --git a/packages/messaging/message-box-client/package.json b/packages/messaging/message-box-client/package.json index c6ed729eb..4cdc5af73 100644 --- a/packages/messaging/message-box-client/package.json +++ b/packages/messaging/message-box-client/package.json @@ -1,6 +1,6 @@ { "name": "@bsv/message-box-client", - "version": "2.4.1", + "version": "2.5.0", "sideEffects": false, "engines": { "node": ">=22" diff --git a/packages/messaging/message-box-client/src/MessageBoxClient.ts b/packages/messaging/message-box-client/src/MessageBoxClient.ts index cd22ab2d2..fb81121a2 100644 --- a/packages/messaging/message-box-client/src/MessageBoxClient.ts +++ b/packages/messaging/message-box-client/src/MessageBoxClient.ts @@ -211,6 +211,7 @@ export class MessageBoxClient { private socketAuthenticated = false private connectionInitPromise?: Promise protected originator?: OriginatorDomainNameStringUnder250Bytes + private readonly socketOptions?: MessageBoxClientOptions['socketOptions'] /** * @constructor * @param {Object} options - Initialization options for the MessageBoxClient. @@ -218,6 +219,7 @@ export class MessageBoxClient { * @param {WalletInterface} options.walletClient - Wallet instance used for authentication, signing, and encryption. * @param {boolean} [options.enableLogging=false] - Whether to enable detailed debug logging to the console. * @param {'local' | 'mainnet' | 'testnet' | 'teratestnet'} [options.networkPreset='mainnet'] - Overlay network preset used for routing and advertisement lookup. + * @param {Omit} [options.socketOptions] - Options forwarded to the underlying AuthSocketClient, e.g. `{ managerOptions: { transports: ['websocket'] } }`. The client's own wallet and originator always win. * * @description * Constructs a new MessageBoxClient. @@ -241,7 +243,8 @@ export class MessageBoxClient { walletClient, enableLogging = false, networkPreset = 'mainnet', - originator = undefined + originator = undefined, + socketOptions = undefined } = options if (networkPreset === 'teratestnet' && host == null) { @@ -257,6 +260,7 @@ export class MessageBoxClient { this.host = normalizeMessageBoxHost(host ?? defaultHost) this.originator = originator + this.socketOptions = socketOptions this.walletClient = walletClient ?? new WalletClient('auto', originator) this.authFetch = new AuthFetch(this.walletClient, undefined, undefined, originator) this.networkPreset = networkPreset @@ -435,6 +439,7 @@ export class MessageBoxClient { if (this.socket == null) { const targetHost = normalizeMessageBoxHost(overrideHost ?? this.host) this.socket = AuthSocketClient(targetHost, { + ...this.socketOptions, wallet: this.walletClient, originator: this.originator }) diff --git a/packages/messaging/message-box-client/src/__tests/MessageBoxClient.test.ts b/packages/messaging/message-box-client/src/__tests/MessageBoxClient.test.ts index f1d7dd5b4..6275fda58 100644 --- a/packages/messaging/message-box-client/src/__tests/MessageBoxClient.test.ts +++ b/packages/messaging/message-box-client/src/__tests/MessageBoxClient.test.ts @@ -29,6 +29,9 @@ jest.unstable_mockModule('@bsv/authsocket-client', () => ({ const { MessageBoxClient } = await import('../MessageBoxClient.js') +const { AuthSocketClient } = await import('@bsv/authsocket-client') +const authSocketClientMock = AuthSocketClient as unknown as jest.Mock + // MOCK: WalletClient methods globally jest.spyOn(WalletClient.prototype, 'createHmac').mockResolvedValue({ hmac: Array.from(new Uint8Array([1, 2, 3])) @@ -166,6 +169,77 @@ describe('MessageBoxClient', () => { await expect(connection).resolves.toBeUndefined() }, 10000) + it('Forwards socketOptions to AuthSocketClient when configured', async () => { + const messageBoxClient = new MessageBoxClient({ + walletClient: mockWalletClient, + host: 'https://message-box-us-1.bsvb.tech', + socketOptions: { + managerOptions: { transports: ['websocket'] }, + maxPendingAuthMessages: 8 + } + }) + + await messageBoxClient.init() + + const connection = messageBoxClient.initializeConnection() + setTimeout(() => { + socketOnMap.authenticationSuccess?.({ status: 'ok' }) + }, 100) + await connection + + expect(authSocketClientMock).toHaveBeenCalledWith( + 'https://message-box-us-1.bsvb.tech', + expect.objectContaining({ + managerOptions: { transports: ['websocket'] }, + maxPendingAuthMessages: 8 + }) + ) + }, 10000) + + it('Passes only client-owned options to AuthSocketClient when socketOptions is omitted', async () => { + const messageBoxClient = new MessageBoxClient({ + walletClient: mockWalletClient, + host: 'https://message-box-us-1.bsvb.tech' + }) + + await messageBoxClient.init() + + const connection = messageBoxClient.initializeConnection() + setTimeout(() => { + socketOnMap.authenticationSuccess?.({ status: 'ok' }) + }, 100) + await connection + + const options = authSocketClientMock.mock.calls[0][1] as Record + expect(Object.keys(options).sort()).toEqual(['originator', 'wallet']) + }, 10000) + + it('Keeps client-owned wallet and originator ahead of socketOptions', async () => { + const foreignWallet = { id: 'foreign-wallet' } + const messageBoxClient = new MessageBoxClient({ + walletClient: mockWalletClient, + host: 'https://message-box-us-1.bsvb.tech', + originator: 'client.example', + // A JS caller can still smuggle these past the Omit<> type. + socketOptions: { + wallet: foreignWallet, + originator: 'socket.example' + } as any + }) + + await messageBoxClient.init() + + const connection = messageBoxClient.initializeConnection() + setTimeout(() => { + socketOnMap.authenticationSuccess?.({ status: 'ok' }) + }, 100) + await connection + + const options = authSocketClientMock.mock.calls[0][1] as Record + expect(options.originator).toBe('client.example') + expect(options.wallet).not.toBe(foreignWallet) + }, 10000) + it('Falls back to HTTP when WebSocket is not initialized', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, diff --git a/packages/messaging/message-box-client/src/types.ts b/packages/messaging/message-box-client/src/types.ts index f29565b97..eea0a92b6 100644 --- a/packages/messaging/message-box-client/src/types.ts +++ b/packages/messaging/message-box-client/src/types.ts @@ -14,6 +14,7 @@ import { WalletInterface, type LookupNetworkPreset } from '@bsv/sdk' +import type { AuthSocketClientOptions } from '@bsv/authsocket-client' /** * Configuration options for initializing a MessageBoxClient. @@ -48,6 +49,19 @@ export interface MessageBoxClientOptions { * Originator of the message box client. */ originator?: string + + /** + * Options forwarded to the underlying AuthSocketClient when the live socket is + * created. Covers socket.io transport selection via `managerOptions` (e.g. + * `{ managerOptions: { transports: ['websocket'] } }` for a deployment that does + * not carry Engine.IO HTTP polling), plus certificate requests, session + * management, auth-message concurrency, and error reporting. + * + * `wallet` and `originator` are excluded because the client owns both; the + * client's own values always win. Only affects the socket path; HTTP requests + * are unchanged. + */ + socketOptions?: Omit } /**