@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.
- package/LICENSE.txt +28 -0
- package/README.md +1013 -0
- package/mod.ts +3 -0
- package/package.json +80 -0
- package/src/MessageBoxClient.ts +1341 -0
- package/src/PeerPayClient.ts +386 -0
- package/src/Utils/logger.ts +27 -0
- package/src/__tests/MessageBoxClient.test.ts +763 -0
- package/src/__tests/PeerPayClientUnit.test.ts +245 -0
- package/src/__tests/integration/integrationEncrypted.test.ts +103 -0
- package/src/__tests/integration/integrationHTTP.test.ts +158 -0
- package/src/__tests/integration/integrationOverlay.test.ts +163 -0
- package/src/__tests/integration/integrationWS.test.ts +147 -0
- package/src/__tests/integration/testServer.ts +68 -0
- package/src/types.ts +108 -0
|
@@ -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
|
+
}
|