@bsv/message-box-client 1.1.7

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.
@@ -0,0 +1,147 @@
1
+ /* eslint-env jest */
2
+ import { MessageBoxClient } from '../../MessageBoxClient.js'
3
+ import { PeerMessage } from '../../types.js'
4
+ import { WalletClient } from '@bsv/sdk'
5
+ import { webcrypto } from 'crypto'
6
+
7
+ (global as any).self = { crypto: webcrypto }
8
+
9
+ jest.setTimeout(20000)
10
+
11
+ const WS_URL = 'https://messagebox.babbage.systems'
12
+
13
+ let recipientKey: string
14
+ const messageBox = 'testBox'
15
+ const testMessage = 'Hello, this is a WebSocket integration test.'
16
+
17
+ const walletClient = new WalletClient('json-api', 'localhost')
18
+ const messageBoxClient = new MessageBoxClient({
19
+ host: WS_URL,
20
+ walletClient
21
+ })
22
+
23
+ describe('MessageBoxClient WebSocket Integration Tests', () => {
24
+ beforeAll(async () => {
25
+ const keyResult = await walletClient.getPublicKey({ identityKey: true })
26
+ recipientKey = keyResult.publicKey
27
+ console.log(`Recipient Key: ${recipientKey}`)
28
+
29
+ await messageBoxClient.initializeConnection()
30
+ })
31
+
32
+ afterAll(async () => {
33
+ console.log('Closing WebSocket connection after tests.')
34
+ await messageBoxClient.disconnectWebSocket()
35
+ })
36
+
37
+ /** TEST 1: Authenticate WebSocket Connection **/
38
+ test('should authenticate and connect via WebSocket', async () => {
39
+ expect(messageBoxClient.testSocket).toBeDefined()
40
+ console.log('[TEST] WebSocket authenticated and connected')
41
+ }, 15000)
42
+
43
+ /** TEST 2: Join a WebSocket Room **/
44
+ test('should join a WebSocket room successfully', async () => {
45
+ await messageBoxClient.joinRoom(messageBox)
46
+ console.log(`[TEST] Joined WebSocket room: ${messageBox}`)
47
+
48
+ const identityKey = await messageBoxClient.getIdentityKey()
49
+ expect(messageBoxClient.getJoinedRooms().has(`${identityKey}-${messageBox}`)).toBe(true)
50
+ }, 15000)
51
+
52
+ /** TEST 3: Send and Receive a Message via WebSocket **/
53
+ test('should send and receive a message via WebSocket', async () => {
54
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
55
+ let receivedMessage: PeerMessage | null = null
56
+
57
+ const messagePromise = new Promise<PeerMessage>((resolve, reject) => {
58
+ messageBoxClient
59
+ .listenForLiveMessages({
60
+ messageBox,
61
+ onMessage: (message: PeerMessage) => {
62
+ try {
63
+ receivedMessage = message
64
+ console.log('[TEST] Received message:', JSON.stringify(message, null, 2))
65
+ resolve(message)
66
+ } catch (error) {
67
+ console.error('[ERROR] Error processing message:', error)
68
+ reject(error)
69
+ }
70
+ }
71
+ })
72
+ .catch(reject)
73
+
74
+ setTimeout(() => {
75
+ reject(new Error('Test timed out: No message received over WebSocket'))
76
+ }, 10000)
77
+ })
78
+
79
+ await messageBoxClient.joinRoom(messageBox)
80
+
81
+ console.log(`[TEST] Sending message to WebSocket room: ${messageBox}`)
82
+
83
+ const response = await messageBoxClient.sendLiveMessage({
84
+ recipient: recipientKey,
85
+ messageBox,
86
+ body: testMessage
87
+ })
88
+
89
+ expect(response).toHaveProperty('status', 'success')
90
+
91
+ const received = await messagePromise
92
+ expect(received).not.toBeNull()
93
+ expect(received.body).toBe(testMessage)
94
+ expect(received.sender).toBe(recipientKey)
95
+ }, 15000)
96
+
97
+ /** TEST 4: Leave a WebSocket Room **/
98
+ test('should leave a WebSocket room successfully', async () => {
99
+ await messageBoxClient.leaveRoom(messageBox)
100
+ console.log(`[TEST] Left WebSocket room: ${messageBox}`)
101
+
102
+ const identityKey = await messageBoxClient.getIdentityKey()
103
+ expect(messageBoxClient.getJoinedRooms().has(`${identityKey}-${messageBox}`)).toBe(false)
104
+ }, 15000)
105
+
106
+ /** TEST 5: Send and Receive a Message via WebSocket without Encryption **/
107
+ test('should send and receive a message via WebSocket without encryption', async () => {
108
+ const unencryptedMessage = 'Plaintext WebSocket message'
109
+
110
+ const messagePromise = new Promise<PeerMessage>((resolve, reject) => {
111
+ messageBoxClient
112
+ .listenForLiveMessages({
113
+ messageBox,
114
+ onMessage: (message: PeerMessage) => {
115
+ try {
116
+ console.log('[TEST] Received unencrypted message:', message)
117
+ resolve(message)
118
+ } catch (error) {
119
+ console.error('[ERROR] Error processing message:', error)
120
+ reject(error)
121
+ }
122
+ }
123
+ })
124
+ .catch(reject)
125
+
126
+ setTimeout(() => {
127
+ reject(new Error('Test timed out: No unencrypted message received'))
128
+ }, 10000)
129
+ })
130
+
131
+ await messageBoxClient.joinRoom(messageBox)
132
+
133
+ const response = await messageBoxClient.sendLiveMessage({
134
+ recipient: recipientKey,
135
+ messageBox,
136
+ body: unencryptedMessage,
137
+ skipEncryption: true
138
+ })
139
+
140
+ expect(response).toHaveProperty('status', 'success')
141
+
142
+ const received = await messagePromise
143
+ expect(received).not.toBeNull()
144
+ expect(received.body).toBe(unencryptedMessage)
145
+ expect(received.sender).toBe(recipientKey)
146
+ }, 15000)
147
+ })
@@ -0,0 +1,68 @@
1
+ import { spawn } from 'child_process'
2
+ import { resolve } from 'path'
3
+
4
+ let serverProcess: any | null = null
5
+
6
+ async function isServerRunning (): Promise<boolean> {
7
+ try {
8
+ await fetch('http://localhost:8080/health') // Use an actual health check route
9
+ return true
10
+ } catch {
11
+ return false
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Starts the MessageBoxServer as a separate process if not already running.
17
+ */
18
+ export async function startTestServer (): Promise<void> {
19
+ if (await isServerRunning()) {
20
+ console.log('Test server already running.')
21
+ return
22
+ }
23
+
24
+ console.log('Starting test server...')
25
+ serverProcess = spawn('npm', ['run', 'dev'], {
26
+ cwd: resolve(__dirname, '../../../MessageBoxServer'),
27
+ stdio: 'inherit',
28
+ shell: true
29
+ })
30
+
31
+ await new Promise((resolve, reject) => {
32
+ const timeout = setTimeout(() => {
33
+ reject(new Error('Test server startup timed out'))
34
+ }, 10000)
35
+
36
+ serverProcess.on('error', (err) => {
37
+ console.error('Test server failed to start:', err)
38
+ clearTimeout(timeout)
39
+ reject(err)
40
+ })
41
+
42
+ setTimeout(() => {
43
+ console.log('Test server started.')
44
+ clearTimeout(timeout)
45
+ resolve(undefined)
46
+ }, 3000)
47
+ })
48
+ }
49
+
50
+ /**
51
+ * Stops the MessageBoxServer process after tests.
52
+ */
53
+ export async function stopTestServer (): Promise<void> {
54
+ if (serverProcess === null) {
55
+ console.warn('Test server process is already stopped or undefined.')
56
+ return
57
+ }
58
+
59
+ console.log('Stopping test server...')
60
+ try {
61
+ serverProcess.kill()
62
+ console.log('Test server stopped.')
63
+ } catch (error) {
64
+ console.error('Error stopping test server:', error)
65
+ }
66
+
67
+ serverProcess = null
68
+ }
package/src/types.ts ADDED
@@ -0,0 +1,108 @@
1
+ import { Base64String, WalletClient } from '@bsv/sdk'
2
+
3
+ /**
4
+ * Configuration options for initializing a MessageBoxClient.
5
+ */
6
+ export interface MessageBoxClientOptions {
7
+ /**
8
+ * Wallet instance used for auth, identity, and encryption.
9
+ * If not provided, a new WalletClient will be created.
10
+ */
11
+ walletClient?: WalletClient
12
+
13
+ /**
14
+ * Base URL of the MessageBox server.
15
+ * @default 'https://messagebox.babbage.systems'
16
+ */
17
+ host?: string
18
+
19
+ /**
20
+ * If true, enables detailed logging to the console.
21
+ * @default false
22
+ */
23
+ enableLogging?: boolean
24
+
25
+ /**
26
+ * Overlay network preset for routing resolution.
27
+ * @default 'local'
28
+ */
29
+ networkPreset?: 'local' | 'mainnet' | 'testnet'
30
+ }
31
+
32
+ /**
33
+ * Represents a decrypted message received from a MessageBox.
34
+ * Includes metadata such as sender identity, timestamps, and optional acknowledgment status.
35
+ *
36
+ * Used in both HTTP and WebSocket message retrieval responses.
37
+ */
38
+ export interface PeerMessage {
39
+ messageId: string
40
+ body: string | Record<string, any>
41
+ sender: string
42
+ created_at: string
43
+ updated_at: string
44
+ acknowledged?: boolean
45
+ }
46
+
47
+ /**
48
+ * Parameters required to send a message.
49
+ * Message content may be a string or object, and encryption is enabled by default.
50
+ *
51
+ * @example
52
+ * {
53
+ * recipient: "03abc...",
54
+ * messageBox: "payment_inbox",
55
+ * body: { type: "ping" },
56
+ * skipEncryption: false
57
+ * }
58
+ */
59
+ export interface SendMessageParams {
60
+ recipient: string
61
+ messageBox: string
62
+ body: string | object
63
+ messageId?: string
64
+ skipEncryption?: boolean
65
+ }
66
+
67
+ /**
68
+ * Server response structure for successful message delivery.
69
+ *
70
+ * Returned by both `sendMessage` and `sendLiveMessage`.
71
+ */
72
+ export interface SendMessageResponse {
73
+ status: string
74
+ messageId: string
75
+ }
76
+
77
+ /**
78
+ * Parameters for acknowledging messages in the system.
79
+ *
80
+ * @interface AcknowledgeMessageParams
81
+ *
82
+ * @property {string[]} messageIds - An array of message IDs to acknowledge.
83
+ * @property {string} [host] - Optional host URL where the messages originated.
84
+ */
85
+ export interface AcknowledgeMessageParams {
86
+ messageIds: string[]
87
+ host?: string
88
+ }
89
+
90
+ /**
91
+ * Parameters for listing messages in a message box.
92
+ *
93
+ * @property messageBox - The identifier of the message box to retrieve messages from.
94
+ * @property host - (Optional) The host URL to connect to for retrieving messages.
95
+ */
96
+ export interface ListMessagesParams {
97
+ messageBox: string
98
+ host?: string
99
+ }
100
+
101
+ /**
102
+ * Encapsulates an AES-256-GCM encrypted message body.
103
+ *
104
+ * Used when transmitting encrypted payloads to the MessageBox server.
105
+ */
106
+ export interface EncryptedMessage {
107
+ encryptedMessage: Base64String
108
+ }