nostr-websocket-utils 0.2.3 โ 0.2.5
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/README.md +76 -148
- package/dist/client.d.ts +105 -0
- package/dist/client.js +105 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/nostr-server.d.ts +31 -0
- package/dist/nostr-server.js +107 -0
- package/dist/server.d.ts +13 -3
- package/dist/server.js +60 -33
- package/dist/types/index.d.ts +176 -1
- package/dist/types/nostr.d.ts +52 -0
- package/dist/types/nostr.js +10 -0
- package/dist/utils/logger.d.ts +5 -1
- package/dist/utils/logger.js +19 -5
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# nostr-websocket-utils
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/nostr-websocket-utils)
|
|
4
|
-
[](https://github.com/HumanjavaEnterprises/nostr-websocket-utils/blob/main/LICENSE)
|
|
3
|
+
[](https://www.npmjs.com/package/@humanjavaenterprises/nostr-websocket-utils)
|
|
4
|
+
[](https://github.com/HumanjavaEnterprises/nostr-websocket-utils/blob/main/LICENSE)
|
|
5
5
|
[](https://github.com/HumanjavaEnterprises/nostr-websocket-utils/actions)
|
|
6
|
+
[](https://humanjavaenterprises.github.io/nostr-websocket-utils/)
|
|
6
7
|
[](https://www.typescriptlang.org)
|
|
7
8
|
[](https://github.com/prettier/prettier)
|
|
8
9
|
|
|
9
|
-
A TypeScript library
|
|
10
|
+
A TypeScript library for building Nostr protocol WebSocket clients and servers.
|
|
10
11
|
|
|
11
12
|
## Features
|
|
12
13
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- ๐งช 100% test coverage with Jest
|
|
21
|
-
- ๐ฆ Zero DOM dependencies
|
|
14
|
+
- ๐ Full Nostr protocol support
|
|
15
|
+
- ๐ Secure WebSocket connections
|
|
16
|
+
- โฅ๏ธ Heartbeat mechanism for connection health
|
|
17
|
+
- ๐ Automatic reconnection handling
|
|
18
|
+
- ๐ Comprehensive logging
|
|
19
|
+
- ๐ฏ Type-safe message handling
|
|
20
|
+
- ๐ฆ Easy to use API
|
|
22
21
|
|
|
23
22
|
## Installation
|
|
24
23
|
|
|
@@ -26,179 +25,108 @@ A TypeScript library providing WebSocket utilities for Nostr applications, focus
|
|
|
26
25
|
npm install nostr-websocket-utils
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
##
|
|
28
|
+
## Quick Start
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
- ๐ Added UUID support for message tracking and correlation
|
|
33
|
-
- ๐ Introduced `WebSocketImpl` option for custom WebSocket implementations
|
|
34
|
-
- ๐ก๏ธ Enhanced TypeScript type safety and validation
|
|
35
|
-
- ๐ Improved reconnection and error handling logic
|
|
36
|
-
- ๐ Added comprehensive logging support
|
|
37
|
-
|
|
38
|
-
## Usage
|
|
39
|
-
|
|
40
|
-
### Server Example
|
|
30
|
+
### Creating a Nostr WebSocket Client
|
|
41
31
|
|
|
42
32
|
```typescript
|
|
43
|
-
import {
|
|
44
|
-
import { WebSocketServer } from 'ws';
|
|
45
|
-
import { getLogger } from './utils/logger';
|
|
46
|
-
|
|
47
|
-
// Create WebSocket server
|
|
48
|
-
const wss = new WebSocketServer({ port: 8080 });
|
|
33
|
+
import { NostrWSClient } from 'nostr-websocket-utils';
|
|
49
34
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
heartbeatInterval: 30000, // Optional: 30 seconds
|
|
35
|
+
const client = new NostrWSClient('wss://relay.example.com', {
|
|
36
|
+
logger: console,
|
|
37
|
+
heartbeatInterval: 30000,
|
|
54
38
|
handlers: {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
case 'subscribe':
|
|
59
|
-
// Handle subscription
|
|
60
|
-
ws.subscriptions?.add(message.data.channel);
|
|
61
|
-
break;
|
|
62
|
-
case 'event':
|
|
63
|
-
// Broadcast to relevant subscribers
|
|
64
|
-
server.broadcastToChannel(message.data.channel, message);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
// Optional: Handle errors
|
|
69
|
-
error: (ws, error) => {
|
|
70
|
-
logger.error('WebSocket error:', error);
|
|
71
|
-
},
|
|
72
|
-
// Optional: Handle client disconnection
|
|
73
|
-
close: (ws) => {
|
|
74
|
-
logger.info(`Client ${ws.clientId} disconnected`);
|
|
75
|
-
}
|
|
39
|
+
message: async (msg) => console.log('Received:', msg),
|
|
40
|
+
error: (err) => console.error('Error:', err),
|
|
41
|
+
close: () => console.log('Connection closed')
|
|
76
42
|
}
|
|
77
43
|
});
|
|
44
|
+
|
|
45
|
+
await client.connect();
|
|
78
46
|
```
|
|
79
47
|
|
|
80
|
-
###
|
|
48
|
+
### Creating a Nostr WebSocket Server
|
|
81
49
|
|
|
82
50
|
```typescript
|
|
83
|
-
import {
|
|
84
|
-
import { getLogger } from './utils/logger';
|
|
51
|
+
import { createNostrServer } from '@humanjavaenterprises/nostr-websocket-utils';
|
|
85
52
|
|
|
86
|
-
const
|
|
87
|
-
logger:
|
|
53
|
+
const server = await createNostrServer(8080, {
|
|
54
|
+
logger: console,
|
|
88
55
|
heartbeatInterval: 30000,
|
|
89
56
|
handlers: {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
57
|
+
message: async (ws, msg) => {
|
|
58
|
+
console.log('Received message:', msg);
|
|
59
|
+
// Handle the message
|
|
93
60
|
}
|
|
94
61
|
}
|
|
95
62
|
});
|
|
63
|
+
```
|
|
96
64
|
|
|
97
|
-
|
|
98
|
-
client.on('connect', () => {
|
|
99
|
-
console.log('Connected to server');
|
|
100
|
-
|
|
101
|
-
// Subscribe to a channel
|
|
102
|
-
client.subscribe('my-channel');
|
|
103
|
-
|
|
104
|
-
// Send an event
|
|
105
|
-
client.send({
|
|
106
|
-
type: 'event',
|
|
107
|
-
data: {
|
|
108
|
-
channel: 'my-channel',
|
|
109
|
-
content: 'Hello, Nostr!'
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
});
|
|
65
|
+
## Documentation
|
|
113
66
|
|
|
114
|
-
|
|
115
|
-
client.connect();
|
|
116
|
-
```
|
|
67
|
+
Comprehensive API documentation is available in our [documentation site](https://humanjavaenterprises.github.io/nostr-websocket-utils/). Here's what you'll find:
|
|
117
68
|
|
|
118
|
-
|
|
69
|
+
### Core Components
|
|
70
|
+
- [NostrWSClient](docs/classes/NostrWSClient.md) - WebSocket client implementation
|
|
71
|
+
- [NostrWSServer](docs/classes/NostrWSServer.md) - WebSocket server implementation
|
|
72
|
+
- [NostrServer](docs/classes/NostrServer.md) - High-level Nostr server
|
|
119
73
|
|
|
120
|
-
###
|
|
74
|
+
### Types and Interfaces
|
|
75
|
+
- [NostrWSMessage](docs/interfaces/NostrWSMessage.md) - Message structure
|
|
76
|
+
- [NostrWSOptions](docs/interfaces/NostrWSOptions.md) - Configuration options
|
|
77
|
+
- [NostrWSSubscription](docs/interfaces/NostrWSSubscription.md) - Subscription interface
|
|
78
|
+
- [ExtendedWebSocket](docs/interfaces/ExtendedWebSocket.md) - Enhanced WebSocket interface
|
|
121
79
|
|
|
122
|
-
|
|
80
|
+
### Utility Functions
|
|
81
|
+
- [createServer](docs/functions/createServer.md) - Server creation helper
|
|
82
|
+
- [getLogger](docs/functions/getLogger.md) - Logging utility
|
|
123
83
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// Broadcast to all connected clients
|
|
129
|
-
broadcast(message: NostrWSMessage): void;
|
|
130
|
-
|
|
131
|
-
// Broadcast to specific channel subscribers
|
|
132
|
-
broadcastToChannel(channel: string, message: NostrWSMessage): void;
|
|
133
|
-
|
|
134
|
-
// Close the server and all connections
|
|
135
|
-
close(): void;
|
|
136
|
-
}
|
|
137
|
-
```
|
|
84
|
+
### Type Definitions
|
|
85
|
+
- [MessageType](docs/enumerations/NostrWSMessageType.md) - Message type enumeration
|
|
86
|
+
- [Global Types](docs/globals.md) - Global type definitions
|
|
138
87
|
|
|
139
|
-
|
|
88
|
+
## Examples
|
|
140
89
|
|
|
141
|
-
|
|
90
|
+
### Subscribe to a Channel
|
|
142
91
|
|
|
143
92
|
```typescript
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Subscribe to a channel
|
|
151
|
-
subscribe(channel: string, filter?: unknown): void;
|
|
152
|
-
|
|
153
|
-
// Unsubscribe from a channel
|
|
154
|
-
unsubscribe(channel: string): void;
|
|
155
|
-
|
|
156
|
-
// Send a message to the server
|
|
157
|
-
send(message: NostrWSMessage): Promise<void>;
|
|
158
|
-
|
|
159
|
-
// Close the connection
|
|
160
|
-
close(): void;
|
|
161
|
-
}
|
|
93
|
+
client.subscribe('my-channel', {
|
|
94
|
+
filter: {
|
|
95
|
+
authors: ['pubkey1', 'pubkey2'],
|
|
96
|
+
kinds: [1]
|
|
97
|
+
}
|
|
98
|
+
});
|
|
162
99
|
```
|
|
163
100
|
|
|
164
|
-
###
|
|
165
|
-
|
|
166
|
-
The standard message format for communication.
|
|
101
|
+
### Broadcast a Message
|
|
167
102
|
|
|
168
103
|
```typescript
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
type: string; // Message type (e.g., 'subscribe', 'event')
|
|
104
|
+
server.broadcast({
|
|
105
|
+
type: 'event',
|
|
172
106
|
data: {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
}
|
|
107
|
+
content: 'Hello everyone!',
|
|
108
|
+
kind: 1
|
|
109
|
+
}
|
|
110
|
+
});
|
|
177
111
|
```
|
|
178
112
|
|
|
179
|
-
##
|
|
113
|
+
## Contributing
|
|
114
|
+
|
|
115
|
+
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
180
116
|
|
|
181
|
-
|
|
182
|
-
- Built specifically for Nostr protocol requirements
|
|
183
|
-
- Efficient pub/sub model with filtered subscriptions
|
|
184
|
-
- Type-safe message handling for all Nostr events
|
|
117
|
+
## License
|
|
185
118
|
|
|
186
|
-
|
|
187
|
-
- Comprehensive error handling and recovery
|
|
188
|
-
- Memory-efficient subscription management
|
|
189
|
-
- Built-in logging and monitoring
|
|
190
|
-
- Extensive test coverage
|
|
119
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
191
120
|
|
|
192
|
-
|
|
193
|
-
- Clear TypeScript definitions
|
|
194
|
-
- Flexible configuration options
|
|
195
|
-
- Detailed documentation
|
|
196
|
-
- Active maintenance
|
|
121
|
+
## Related Projects
|
|
197
122
|
|
|
198
|
-
|
|
123
|
+
- [nostr-protocol](https://github.com/nostr-protocol/nostr)
|
|
124
|
+
- [nostr-tools](https://github.com/nbd-wtf/nostr-tools)
|
|
199
125
|
|
|
200
|
-
|
|
126
|
+
## Support
|
|
201
127
|
|
|
202
|
-
|
|
128
|
+
If you have any questions or need help, please:
|
|
203
129
|
|
|
204
|
-
|
|
130
|
+
1. Check the [documentation](https://humanjavaenterprises.github.io/nostr-websocket-utils/)
|
|
131
|
+
2. Open an [issue](https://github.com/HumanjavaEnterprises/nostr-websocket-utils/issues)
|
|
132
|
+
3. Join our [Discord community](https://discord.gg/your-discord)
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import type { NostrWSOptions, NostrWSMessage } from './types/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* WebSocket client implementation for Nostr protocol communication
|
|
6
|
+
* Extends EventEmitter to provide event-based message handling
|
|
7
|
+
*
|
|
8
|
+
* @extends EventEmitter
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const client = new NostrWSClient('wss://relay.example.com', {
|
|
12
|
+
* logger: console,
|
|
13
|
+
* heartbeatInterval: 30000,
|
|
14
|
+
* handlers: {
|
|
15
|
+
* message: async (msg) => console.log('Received:', msg),
|
|
16
|
+
* error: (err) => console.error('Error:', err),
|
|
17
|
+
* close: () => console.log('Connection closed')
|
|
18
|
+
* }
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await client.connect();
|
|
22
|
+
* client.send({ type: 'EVENT', data: { ... } });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
4
25
|
export declare class NostrWSClient extends EventEmitter {
|
|
5
26
|
private url;
|
|
6
27
|
private ws;
|
|
@@ -10,15 +31,99 @@ export declare class NostrWSClient extends EventEmitter {
|
|
|
10
31
|
private reconnectAttempts;
|
|
11
32
|
private messageQueue;
|
|
12
33
|
private clientId;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new NostrWSClient instance
|
|
36
|
+
*
|
|
37
|
+
* @param {string} url - The WebSocket server URL to connect to
|
|
38
|
+
* @param {Partial<NostrWSOptions>} options - Configuration options
|
|
39
|
+
* @param {number} [options.heartbeatInterval=30000] - Interval for sending heartbeat messages in milliseconds
|
|
40
|
+
* @param {object} options.logger - Logger instance (required)
|
|
41
|
+
* @param {Function} [options.WebSocketImpl=WebSocket] - WebSocket implementation to use
|
|
42
|
+
* @param {object} [options.handlers] - Event handlers
|
|
43
|
+
* @param {Function} [options.handlers.message] - Message handler function
|
|
44
|
+
* @param {Function} [options.handlers.error] - Error handler function
|
|
45
|
+
* @param {Function} [options.handlers.close] - Connection close handler function
|
|
46
|
+
* @throws {Error} If logger is not provided
|
|
47
|
+
*/
|
|
13
48
|
constructor(url: string, options?: Partial<NostrWSOptions>);
|
|
49
|
+
/**
|
|
50
|
+
* Establishes a connection to the WebSocket server
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* client.connect();
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
14
57
|
connect(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Sets up event handlers for the WebSocket connection
|
|
60
|
+
*
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
15
63
|
private setupEventHandlers;
|
|
64
|
+
/**
|
|
65
|
+
* Starts sending heartbeat messages at the specified interval
|
|
66
|
+
*
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
16
69
|
private startHeartbeat;
|
|
70
|
+
/**
|
|
71
|
+
* Stops sending heartbeat messages
|
|
72
|
+
*
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
17
75
|
private stopHeartbeat;
|
|
76
|
+
/**
|
|
77
|
+
* Handles reconnecting to the WebSocket server after a disconnection
|
|
78
|
+
*
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
18
81
|
private handleReconnect;
|
|
82
|
+
/**
|
|
83
|
+
* Subscribes to a channel with optional filter
|
|
84
|
+
*
|
|
85
|
+
* @param {string} channel - Channel name
|
|
86
|
+
* @param {unknown} [filter] - Filter data
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* client.subscribe('channel-name', { ...filterData });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
19
92
|
subscribe(channel: string, filter?: unknown): void;
|
|
93
|
+
/**
|
|
94
|
+
* Unsubscribes from a channel
|
|
95
|
+
*
|
|
96
|
+
* @param {string} channel - Channel name
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* client.unsubscribe('channel-name');
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
20
102
|
unsubscribe(channel: string): void;
|
|
103
|
+
/**
|
|
104
|
+
* Flushes the message queue by sending pending messages
|
|
105
|
+
*
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
21
108
|
private flushMessageQueue;
|
|
109
|
+
/**
|
|
110
|
+
* Sends a message to the WebSocket server
|
|
111
|
+
*
|
|
112
|
+
* @param {NostrWSMessage} message - Message to send
|
|
113
|
+
* @returns {Promise<void>}
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* client.send({ type: 'EVENT', data: { ... } });
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
22
119
|
send(message: NostrWSMessage): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Closes the WebSocket connection
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* client.close();
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
23
128
|
close(): void;
|
|
24
129
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from 'uuid';
|
|
2
2
|
import WebSocket from 'ws';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
|
+
/**
|
|
5
|
+
* WebSocket client implementation for Nostr protocol communication
|
|
6
|
+
* Extends EventEmitter to provide event-based message handling
|
|
7
|
+
*
|
|
8
|
+
* @extends EventEmitter
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const client = new NostrWSClient('wss://relay.example.com', {
|
|
12
|
+
* logger: console,
|
|
13
|
+
* heartbeatInterval: 30000,
|
|
14
|
+
* handlers: {
|
|
15
|
+
* message: async (msg) => console.log('Received:', msg),
|
|
16
|
+
* error: (err) => console.error('Error:', err),
|
|
17
|
+
* close: () => console.log('Connection closed')
|
|
18
|
+
* }
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await client.connect();
|
|
22
|
+
* client.send({ type: 'EVENT', data: { ... } });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
4
25
|
export class NostrWSClient extends EventEmitter {
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new NostrWSClient instance
|
|
28
|
+
*
|
|
29
|
+
* @param {string} url - The WebSocket server URL to connect to
|
|
30
|
+
* @param {Partial<NostrWSOptions>} options - Configuration options
|
|
31
|
+
* @param {number} [options.heartbeatInterval=30000] - Interval for sending heartbeat messages in milliseconds
|
|
32
|
+
* @param {object} options.logger - Logger instance (required)
|
|
33
|
+
* @param {Function} [options.WebSocketImpl=WebSocket] - WebSocket implementation to use
|
|
34
|
+
* @param {object} [options.handlers] - Event handlers
|
|
35
|
+
* @param {Function} [options.handlers.message] - Message handler function
|
|
36
|
+
* @param {Function} [options.handlers.error] - Error handler function
|
|
37
|
+
* @param {Function} [options.handlers.close] - Connection close handler function
|
|
38
|
+
* @throws {Error} If logger is not provided
|
|
39
|
+
*/
|
|
5
40
|
constructor(url, options = {}) {
|
|
6
41
|
super();
|
|
7
42
|
this.url = url;
|
|
@@ -25,6 +60,14 @@ export class NostrWSClient extends EventEmitter {
|
|
|
25
60
|
}
|
|
26
61
|
};
|
|
27
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Establishes a connection to the WebSocket server
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* client.connect();
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
28
71
|
connect() {
|
|
29
72
|
if (this.ws) {
|
|
30
73
|
this.options.logger.info('WebSocket connection already exists');
|
|
@@ -41,6 +84,11 @@ export class NostrWSClient extends EventEmitter {
|
|
|
41
84
|
this.handleReconnect();
|
|
42
85
|
}
|
|
43
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Sets up event handlers for the WebSocket connection
|
|
89
|
+
*
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
44
92
|
setupEventHandlers() {
|
|
45
93
|
if (!this.ws)
|
|
46
94
|
return;
|
|
@@ -81,6 +129,11 @@ export class NostrWSClient extends EventEmitter {
|
|
|
81
129
|
}
|
|
82
130
|
});
|
|
83
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Starts sending heartbeat messages at the specified interval
|
|
134
|
+
*
|
|
135
|
+
* @private
|
|
136
|
+
*/
|
|
84
137
|
startHeartbeat() {
|
|
85
138
|
if (this.heartbeatInterval)
|
|
86
139
|
return;
|
|
@@ -90,12 +143,22 @@ export class NostrWSClient extends EventEmitter {
|
|
|
90
143
|
}
|
|
91
144
|
}, this.options.heartbeatInterval || 30000);
|
|
92
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Stops sending heartbeat messages
|
|
148
|
+
*
|
|
149
|
+
* @private
|
|
150
|
+
*/
|
|
93
151
|
stopHeartbeat() {
|
|
94
152
|
if (this.heartbeatInterval) {
|
|
95
153
|
clearInterval(this.heartbeatInterval);
|
|
96
154
|
this.heartbeatInterval = null;
|
|
97
155
|
}
|
|
98
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Handles reconnecting to the WebSocket server after a disconnection
|
|
159
|
+
*
|
|
160
|
+
* @private
|
|
161
|
+
*/
|
|
99
162
|
handleReconnect() {
|
|
100
163
|
if (this.reconnectTimeout)
|
|
101
164
|
return;
|
|
@@ -106,18 +169,42 @@ export class NostrWSClient extends EventEmitter {
|
|
|
106
169
|
this.emit('reconnect');
|
|
107
170
|
}, 5000);
|
|
108
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Subscribes to a channel with optional filter
|
|
174
|
+
*
|
|
175
|
+
* @param {string} channel - Channel name
|
|
176
|
+
* @param {unknown} [filter] - Filter data
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* client.subscribe('channel-name', { ...filterData });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
109
182
|
subscribe(channel, filter) {
|
|
110
183
|
this.send({
|
|
111
184
|
type: 'subscribe',
|
|
112
185
|
data: { channel, filter }
|
|
113
186
|
});
|
|
114
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Unsubscribes from a channel
|
|
190
|
+
*
|
|
191
|
+
* @param {string} channel - Channel name
|
|
192
|
+
* @example
|
|
193
|
+
* ```typescript
|
|
194
|
+
* client.unsubscribe('channel-name');
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
115
197
|
unsubscribe(channel) {
|
|
116
198
|
this.send({
|
|
117
199
|
type: 'unsubscribe',
|
|
118
200
|
data: { channel }
|
|
119
201
|
});
|
|
120
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Flushes the message queue by sending pending messages
|
|
205
|
+
*
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
121
208
|
flushMessageQueue() {
|
|
122
209
|
while (this.messageQueue.length > 0 && this.ws?.readyState === WebSocket.OPEN) {
|
|
123
210
|
const message = this.messageQueue.shift();
|
|
@@ -126,6 +213,16 @@ export class NostrWSClient extends EventEmitter {
|
|
|
126
213
|
}
|
|
127
214
|
}
|
|
128
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Sends a message to the WebSocket server
|
|
218
|
+
*
|
|
219
|
+
* @param {NostrWSMessage} message - Message to send
|
|
220
|
+
* @returns {Promise<void>}
|
|
221
|
+
* @example
|
|
222
|
+
* ```typescript
|
|
223
|
+
* client.send({ type: 'EVENT', data: { ... } });
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
129
226
|
async send(message) {
|
|
130
227
|
message.id = message.id || uuidv4();
|
|
131
228
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
@@ -140,6 +237,14 @@ export class NostrWSClient extends EventEmitter {
|
|
|
140
237
|
this.handleReconnect();
|
|
141
238
|
}
|
|
142
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Closes the WebSocket connection
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* client.close();
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
143
248
|
close() {
|
|
144
249
|
this.stopHeartbeat();
|
|
145
250
|
if (this.reconnectTimeout) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Main entry point for the nostr-websocket-utils library
|
|
3
|
+
* @module nostr-websocket-utils
|
|
4
|
+
*/
|
|
1
5
|
export { NostrWSClient } from './client.js';
|
|
2
6
|
export { NostrWSServer } from './server.js';
|
|
7
|
+
export { NostrWSServer as NostrServer } from './nostr-server.js';
|
|
8
|
+
export { createWSServer as createServer } from './nostr-server.js';
|
|
3
9
|
export { getLogger } from './utils/logger.js';
|
|
4
10
|
export type { NostrWSOptions, NostrWSMessage, NostrWSSubscription, NostrWSClientEvents, NostrWSServerEvents, ExtendedWebSocket, NostrWSValidationResult, NostrWSConnectionState } from './types/index.js';
|
|
11
|
+
export type { NostrWSEvent as NostrEvent, NostrWSFilter as NostrFilter, NostrWSSocket as NostrSocket, NostrWSServerOptions, NostrWSServerMessage, NostrWSMessageType } from './types/nostr.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Main entry point for the nostr-websocket-utils library
|
|
3
|
+
* @module nostr-websocket-utils
|
|
4
|
+
*/
|
|
1
5
|
export { NostrWSClient } from './client.js';
|
|
2
6
|
export { NostrWSServer } from './server.js';
|
|
7
|
+
export { NostrWSServer as NostrServer } from './nostr-server.js';
|
|
8
|
+
export { createWSServer as createServer } from './nostr-server.js';
|
|
3
9
|
export { getLogger } from './utils/logger.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NostrWSServerOptions } from './types/nostr';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a Nostr WebSocket server
|
|
4
|
+
*/
|
|
5
|
+
export declare class NostrWSServer {
|
|
6
|
+
/**
|
|
7
|
+
* The underlying WebSocket server instance
|
|
8
|
+
*/
|
|
9
|
+
private server;
|
|
10
|
+
/**
|
|
11
|
+
* Logger instance for this server
|
|
12
|
+
*/
|
|
13
|
+
private logger;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new Nostr WebSocket server instance
|
|
16
|
+
*
|
|
17
|
+
* @param {NostrWSServerOptions} options - Server configuration options
|
|
18
|
+
*/
|
|
19
|
+
constructor(options: NostrWSServerOptions);
|
|
20
|
+
/**
|
|
21
|
+
* Closes the WebSocket server
|
|
22
|
+
*/
|
|
23
|
+
close(): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new Nostr WebSocket server instance
|
|
27
|
+
*
|
|
28
|
+
* @param {NostrWSServerOptions} options - Server configuration options
|
|
29
|
+
* @returns {NostrWSServer} The created server instance
|
|
30
|
+
*/
|
|
31
|
+
export declare function createWSServer(options: NostrWSServerOptions): NostrWSServer;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Server as WebSocketServer } from 'ws';
|
|
2
|
+
import { getLogger } from './utils/logger';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a Nostr WebSocket server
|
|
5
|
+
*/
|
|
6
|
+
export class NostrWSServer {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new Nostr WebSocket server instance
|
|
9
|
+
*
|
|
10
|
+
* @param {NostrWSServerOptions} options - Server configuration options
|
|
11
|
+
*/
|
|
12
|
+
constructor(options) {
|
|
13
|
+
/**
|
|
14
|
+
* Logger instance for this server
|
|
15
|
+
*/
|
|
16
|
+
this.logger = getLogger('NostrWSServer');
|
|
17
|
+
this.server = new WebSocketServer({ port: options.port });
|
|
18
|
+
/**
|
|
19
|
+
* Handles incoming WebSocket connections
|
|
20
|
+
*
|
|
21
|
+
* @param {NostrWSSocket} socket - The connected WebSocket client
|
|
22
|
+
*/
|
|
23
|
+
this.server.on('connection', (socket) => {
|
|
24
|
+
socket.subscriptions = new Set();
|
|
25
|
+
socket.isAlive = true;
|
|
26
|
+
if (options.onConnection) {
|
|
27
|
+
options.onConnection(socket);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Handles incoming messages from the client
|
|
31
|
+
*
|
|
32
|
+
* @param {Buffer | ArrayBuffer | Buffer[]} data - The incoming message data
|
|
33
|
+
*/
|
|
34
|
+
const handleMessage = async (data) => {
|
|
35
|
+
try {
|
|
36
|
+
const message = JSON.parse(data.toString());
|
|
37
|
+
if (options.handlers?.message) {
|
|
38
|
+
await options.handlers.message(socket, message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (options.handlers?.error) {
|
|
43
|
+
options.handlers.error(socket, error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
socket.on('message', handleMessage);
|
|
48
|
+
/**
|
|
49
|
+
* Handles client disconnections
|
|
50
|
+
*/
|
|
51
|
+
socket.on('close', () => {
|
|
52
|
+
if (options.handlers?.close) {
|
|
53
|
+
options.handlers.close(socket);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Handles WebSocket errors
|
|
58
|
+
*
|
|
59
|
+
* @param {Error} error - The error that occurred
|
|
60
|
+
*/
|
|
61
|
+
socket.on('error', (error) => {
|
|
62
|
+
if (options.handlers?.error) {
|
|
63
|
+
options.handlers.error(socket, error);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// Setup heartbeat
|
|
67
|
+
if (options.heartbeatInterval) {
|
|
68
|
+
const interval = setInterval(() => {
|
|
69
|
+
if (!socket.isAlive) {
|
|
70
|
+
socket.terminate();
|
|
71
|
+
clearInterval(interval);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
socket.isAlive = false;
|
|
75
|
+
socket.ping();
|
|
76
|
+
}, options.heartbeatInterval);
|
|
77
|
+
/**
|
|
78
|
+
* Handles WebSocket pong responses
|
|
79
|
+
*/
|
|
80
|
+
socket.on('pong', () => {
|
|
81
|
+
socket.isAlive = true;
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* Handles client disconnections (again, to clear the interval)
|
|
85
|
+
*/
|
|
86
|
+
socket.on('close', () => {
|
|
87
|
+
clearInterval(interval);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Closes the WebSocket server
|
|
94
|
+
*/
|
|
95
|
+
close() {
|
|
96
|
+
this.server.close();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Creates a new Nostr WebSocket server instance
|
|
101
|
+
*
|
|
102
|
+
* @param {NostrWSServerOptions} options - Server configuration options
|
|
103
|
+
* @returns {NostrWSServer} The created server instance
|
|
104
|
+
*/
|
|
105
|
+
export function createWSServer(options) {
|
|
106
|
+
return new NostrWSServer(options);
|
|
107
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { EventEmitter } from 'events';
|
|
3
2
|
import { WebSocketServer } from 'ws';
|
|
4
|
-
import
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import type { NostrWSOptions, NostrWSMessage } from './types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* WebSocket server implementation for Nostr protocol
|
|
7
|
+
* Extends EventEmitter to provide event-based message handling
|
|
8
|
+
*/
|
|
5
9
|
export declare class NostrWSServer extends EventEmitter {
|
|
6
10
|
private wss;
|
|
7
11
|
private options;
|
|
12
|
+
private clients;
|
|
8
13
|
private heartbeatInterval;
|
|
9
|
-
clients: Map<string, ExtendedWebSocket>;
|
|
10
14
|
constructor(wss: WebSocketServer, options?: Partial<NostrWSOptions>);
|
|
11
15
|
private setupServer;
|
|
12
16
|
private handleConnection;
|
|
@@ -14,4 +18,10 @@ export declare class NostrWSServer extends EventEmitter {
|
|
|
14
18
|
broadcast(message: NostrWSMessage): void;
|
|
15
19
|
broadcastToChannel(channel: string, message: NostrWSMessage): void;
|
|
16
20
|
close(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Check if a client with the given ID exists
|
|
23
|
+
* @param clientId - The ID of the client to check
|
|
24
|
+
* @returns boolean indicating if the client exists
|
|
25
|
+
*/
|
|
26
|
+
hasClient(clientId: string): boolean;
|
|
17
27
|
}
|
package/dist/server.js
CHANGED
|
@@ -1,50 +1,59 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
1
|
import { WebSocket } from 'ws';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
/**
|
|
5
|
+
* WebSocket server implementation for Nostr protocol
|
|
6
|
+
* Extends EventEmitter to provide event-based message handling
|
|
7
|
+
*/
|
|
4
8
|
export class NostrWSServer extends EventEmitter {
|
|
5
9
|
constructor(wss, options = {}) {
|
|
6
10
|
super();
|
|
7
|
-
this.
|
|
11
|
+
this.wss = null;
|
|
8
12
|
this.clients = new Map();
|
|
13
|
+
this.heartbeatInterval = null;
|
|
9
14
|
if (!options.logger) {
|
|
10
15
|
throw new Error('Logger is required');
|
|
11
16
|
}
|
|
12
|
-
|
|
13
|
-
throw new Error('Message handler is required');
|
|
14
|
-
}
|
|
17
|
+
this.wss = wss;
|
|
15
18
|
this.options = {
|
|
16
|
-
heartbeatInterval:
|
|
19
|
+
heartbeatInterval: 30000,
|
|
17
20
|
logger: options.logger,
|
|
18
|
-
WebSocketImpl:
|
|
21
|
+
WebSocketImpl: WebSocket,
|
|
22
|
+
...options,
|
|
19
23
|
handlers: {
|
|
20
|
-
message:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
+
message: async (_ws, _message) => { },
|
|
25
|
+
...options.handlers,
|
|
26
|
+
},
|
|
24
27
|
};
|
|
25
|
-
this.wss = wss;
|
|
26
28
|
this.setupServer();
|
|
27
29
|
}
|
|
28
30
|
setupServer() {
|
|
31
|
+
if (!this.wss)
|
|
32
|
+
return;
|
|
29
33
|
this.wss.on('connection', (ws) => {
|
|
30
|
-
|
|
34
|
+
const extWs = ws;
|
|
35
|
+
this.handleConnection(extWs);
|
|
31
36
|
});
|
|
32
|
-
if (this.options.heartbeatInterval
|
|
37
|
+
if (this.options.heartbeatInterval) {
|
|
33
38
|
this.startHeartbeat();
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
handleConnection(ws) {
|
|
37
42
|
ws.isAlive = true;
|
|
38
43
|
ws.subscriptions = new Set();
|
|
39
|
-
ws.clientId =
|
|
44
|
+
ws.clientId = uuidv4();
|
|
45
|
+
ws.messageQueue = [];
|
|
40
46
|
this.clients.set(ws.clientId, ws);
|
|
41
47
|
ws.on('message', async (data) => {
|
|
42
48
|
try {
|
|
43
49
|
const message = JSON.parse(data.toString());
|
|
44
|
-
|
|
50
|
+
if (this.options.handlers?.message) {
|
|
51
|
+
await this.options.handlers.message(ws, message);
|
|
52
|
+
}
|
|
45
53
|
}
|
|
46
54
|
catch (error) {
|
|
47
|
-
|
|
55
|
+
this.options.logger.error('Error handling message:', error);
|
|
56
|
+
if (this.options.handlers?.error) {
|
|
48
57
|
this.options.handlers.error(ws, error);
|
|
49
58
|
}
|
|
50
59
|
}
|
|
@@ -53,35 +62,39 @@ export class NostrWSServer extends EventEmitter {
|
|
|
53
62
|
if (ws.clientId) {
|
|
54
63
|
this.clients.delete(ws.clientId);
|
|
55
64
|
}
|
|
56
|
-
if (this.options.handlers
|
|
65
|
+
if (this.options.handlers?.close) {
|
|
57
66
|
this.options.handlers.close(ws);
|
|
58
67
|
}
|
|
59
68
|
});
|
|
60
69
|
ws.on('error', (error) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
if (this.options.handlers.error) {
|
|
70
|
+
this.options.logger.error('WebSocket error:', error);
|
|
71
|
+
if (this.options.handlers?.error) {
|
|
65
72
|
this.options.handlers.error(ws, error);
|
|
66
73
|
}
|
|
67
74
|
});
|
|
68
75
|
}
|
|
69
76
|
startHeartbeat() {
|
|
77
|
+
if (this.heartbeatInterval)
|
|
78
|
+
return;
|
|
70
79
|
this.heartbeatInterval = setInterval(() => {
|
|
71
|
-
this.wss
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
if (!this.wss)
|
|
81
|
+
return;
|
|
82
|
+
this.wss.clients.forEach((client) => {
|
|
83
|
+
const extClient = client;
|
|
84
|
+
if (extClient.isAlive === false) {
|
|
85
|
+
if (extClient.clientId) {
|
|
86
|
+
this.clients.delete(extClient.clientId);
|
|
87
|
+
}
|
|
88
|
+
return extClient.terminate();
|
|
80
89
|
}
|
|
90
|
+
extClient.isAlive = false;
|
|
91
|
+
extClient.ping();
|
|
81
92
|
});
|
|
82
93
|
}, this.options.heartbeatInterval);
|
|
83
94
|
}
|
|
84
95
|
broadcast(message) {
|
|
96
|
+
if (!this.wss)
|
|
97
|
+
return;
|
|
85
98
|
this.wss.clients.forEach((client) => {
|
|
86
99
|
if (client.readyState === WebSocket.OPEN) {
|
|
87
100
|
client.send(JSON.stringify(message));
|
|
@@ -89,17 +102,31 @@ export class NostrWSServer extends EventEmitter {
|
|
|
89
102
|
});
|
|
90
103
|
}
|
|
91
104
|
broadcastToChannel(channel, message) {
|
|
105
|
+
if (!this.wss)
|
|
106
|
+
return;
|
|
92
107
|
this.wss.clients.forEach((ws) => {
|
|
93
108
|
const extWs = ws;
|
|
94
109
|
if (extWs.readyState === WebSocket.OPEN && extWs.subscriptions?.has(channel)) {
|
|
95
|
-
|
|
110
|
+
extWs.send(JSON.stringify(message));
|
|
96
111
|
}
|
|
97
112
|
});
|
|
98
113
|
}
|
|
99
114
|
close() {
|
|
100
115
|
if (this.heartbeatInterval) {
|
|
101
116
|
clearInterval(this.heartbeatInterval);
|
|
117
|
+
this.heartbeatInterval = null;
|
|
102
118
|
}
|
|
103
|
-
this.wss
|
|
119
|
+
if (this.wss) {
|
|
120
|
+
this.wss.close();
|
|
121
|
+
this.wss = null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check if a client with the given ID exists
|
|
126
|
+
* @param clientId - The ID of the client to check
|
|
127
|
+
* @returns boolean indicating if the client exists
|
|
128
|
+
*/
|
|
129
|
+
hasClient(clientId) {
|
|
130
|
+
return this.clients.has(clientId);
|
|
104
131
|
}
|
|
105
132
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,58 +1,233 @@
|
|
|
1
1
|
import type { WebSocket } from 'ws';
|
|
2
|
+
/**
|
|
3
|
+
* Type of message that can be sent through the WebSocket connection
|
|
4
|
+
* @type {('subscribe' | 'unsubscribe' | 'event' | 'request' | 'response' | 'error' | 'status')}
|
|
5
|
+
*/
|
|
2
6
|
export type MessageType = 'subscribe' | 'unsubscribe' | 'event' | 'request' | 'response' | 'error' | 'status';
|
|
7
|
+
/**
|
|
8
|
+
* Configuration options for the NostrWSClient
|
|
9
|
+
* @interface NostrWSOptions
|
|
10
|
+
*/
|
|
3
11
|
export interface NostrWSOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Interval in milliseconds between heartbeat messages
|
|
14
|
+
* @default 30000
|
|
15
|
+
*/
|
|
4
16
|
heartbeatInterval?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Interval in milliseconds between reconnect attempts
|
|
19
|
+
* @default 5000
|
|
20
|
+
*/
|
|
5
21
|
reconnectInterval?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum number of reconnect attempts
|
|
24
|
+
* @default 10
|
|
25
|
+
*/
|
|
6
26
|
maxReconnectAttempts?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Logger instance for handling log messages
|
|
29
|
+
* Must implement debug, info, error, and warn methods
|
|
30
|
+
*/
|
|
7
31
|
logger: Logger;
|
|
32
|
+
/**
|
|
33
|
+
* WebSocket implementation to use
|
|
34
|
+
* Defaults to the native WebSocket implementation
|
|
35
|
+
*/
|
|
8
36
|
WebSocketImpl: typeof WebSocket;
|
|
37
|
+
/**
|
|
38
|
+
* Event handlers for WebSocket events
|
|
39
|
+
*/
|
|
9
40
|
handlers: {
|
|
41
|
+
/**
|
|
42
|
+
* Handler for incoming messages
|
|
43
|
+
* @param ws - The WebSocket instance
|
|
44
|
+
* @param message - The received message
|
|
45
|
+
*/
|
|
10
46
|
message: (ws: ExtendedWebSocket, message: NostrWSMessage) => Promise<void> | void;
|
|
47
|
+
/**
|
|
48
|
+
* Handler for WebSocket errors
|
|
49
|
+
* @param ws - The WebSocket instance
|
|
50
|
+
* @param error - The error object
|
|
51
|
+
*/
|
|
11
52
|
error?: (ws: WebSocket, error: Error) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Handler for WebSocket connection close
|
|
55
|
+
* @param ws - The WebSocket instance
|
|
56
|
+
*/
|
|
12
57
|
close?: (ws: WebSocket) => void;
|
|
13
58
|
};
|
|
14
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Structure of messages sent through the WebSocket connection
|
|
62
|
+
* @interface NostrWSMessage
|
|
63
|
+
*/
|
|
15
64
|
export interface NostrWSMessage {
|
|
16
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Type of the message (e.g., 'EVENT', 'subscribe', 'unsubscribe')
|
|
67
|
+
*/
|
|
68
|
+
type: MessageType;
|
|
69
|
+
/**
|
|
70
|
+
* Unique identifier for the message
|
|
71
|
+
*/
|
|
17
72
|
id?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Data payload of the message
|
|
75
|
+
*/
|
|
18
76
|
data: Record<string, unknown>;
|
|
19
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Represents a subscription to a Nostr relay
|
|
80
|
+
* @interface NostrWSSubscription
|
|
81
|
+
*/
|
|
20
82
|
export interface NostrWSSubscription {
|
|
83
|
+
/**
|
|
84
|
+
* Channel identifier for the subscription
|
|
85
|
+
*/
|
|
21
86
|
channel: string;
|
|
87
|
+
/**
|
|
88
|
+
* Filter criteria for the subscription
|
|
89
|
+
*/
|
|
22
90
|
filter?: Record<string, unknown>;
|
|
23
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Events emitted by the NostrWSClient
|
|
94
|
+
* @interface NostrWSClientEvents
|
|
95
|
+
*/
|
|
24
96
|
export interface NostrWSClientEvents {
|
|
97
|
+
/**
|
|
98
|
+
* Emitted when the client connects to the relay
|
|
99
|
+
*/
|
|
25
100
|
connect: () => void;
|
|
101
|
+
/**
|
|
102
|
+
* Emitted when the client disconnects from the relay
|
|
103
|
+
*/
|
|
26
104
|
disconnect: () => void;
|
|
105
|
+
/**
|
|
106
|
+
* Emitted when the client reconnects to the relay
|
|
107
|
+
*/
|
|
27
108
|
reconnect: () => void;
|
|
109
|
+
/**
|
|
110
|
+
* Emitted when a message is received from the relay
|
|
111
|
+
* @param message - The received message
|
|
112
|
+
*/
|
|
28
113
|
message: (message: NostrWSMessage) => void;
|
|
114
|
+
/**
|
|
115
|
+
* Emitted when an error occurs
|
|
116
|
+
* @param error - The error object
|
|
117
|
+
*/
|
|
29
118
|
error: (error: Error) => void;
|
|
30
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Events emitted by the NostrWSServer
|
|
122
|
+
* @interface NostrWSServerEvents
|
|
123
|
+
*/
|
|
31
124
|
export interface NostrWSServerEvents {
|
|
125
|
+
/**
|
|
126
|
+
* Emitted when a client connects to the server
|
|
127
|
+
* @param client - The connected client
|
|
128
|
+
*/
|
|
32
129
|
connection: (client: ExtendedWebSocket) => void;
|
|
130
|
+
/**
|
|
131
|
+
* Emitted when a message is received from a client
|
|
132
|
+
* @param message - The received message
|
|
133
|
+
* @param client - The client that sent the message
|
|
134
|
+
*/
|
|
33
135
|
message: (message: NostrWSMessage, client: ExtendedWebSocket) => void;
|
|
136
|
+
/**
|
|
137
|
+
* Emitted when an error occurs
|
|
138
|
+
* @param error - The error object
|
|
139
|
+
*/
|
|
34
140
|
error: (error: Error) => void;
|
|
35
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Extended WebSocket interface with additional properties
|
|
144
|
+
* @interface ExtendedWebSocket
|
|
145
|
+
*/
|
|
36
146
|
export interface ExtendedWebSocket extends WebSocket {
|
|
147
|
+
/**
|
|
148
|
+
* Whether the WebSocket connection is alive
|
|
149
|
+
*/
|
|
37
150
|
isAlive?: boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Set of subscription channels
|
|
153
|
+
*/
|
|
38
154
|
subscriptions?: Set<string>;
|
|
155
|
+
/**
|
|
156
|
+
* Unique client identifier
|
|
157
|
+
*/
|
|
39
158
|
clientId?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Queue of messages to be sent
|
|
161
|
+
*/
|
|
40
162
|
messageQueue?: NostrWSMessage[];
|
|
163
|
+
/**
|
|
164
|
+
* Timestamp of the last ping message
|
|
165
|
+
*/
|
|
41
166
|
lastPing?: number;
|
|
167
|
+
/**
|
|
168
|
+
* Number of reconnect attempts
|
|
169
|
+
*/
|
|
42
170
|
reconnectAttempts?: number;
|
|
43
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Result of validating a NostrWSMessage
|
|
174
|
+
* @interface NostrWSValidationResult
|
|
175
|
+
*/
|
|
44
176
|
export interface NostrWSValidationResult {
|
|
177
|
+
/**
|
|
178
|
+
* Whether the message is valid
|
|
179
|
+
*/
|
|
45
180
|
isValid: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Error message if the message is invalid
|
|
183
|
+
*/
|
|
46
184
|
error?: string;
|
|
47
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* State of the NostrWSClient connection
|
|
188
|
+
* @interface NostrWSConnectionState
|
|
189
|
+
*/
|
|
48
190
|
export interface NostrWSConnectionState {
|
|
191
|
+
/**
|
|
192
|
+
* Whether the client is connected to the relay
|
|
193
|
+
*/
|
|
49
194
|
isConnected: boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Number of reconnect attempts
|
|
197
|
+
*/
|
|
50
198
|
reconnectAttempts: number;
|
|
199
|
+
/**
|
|
200
|
+
* Last error message
|
|
201
|
+
*/
|
|
51
202
|
lastError?: string;
|
|
52
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Logger interface
|
|
206
|
+
* @interface Logger
|
|
207
|
+
*/
|
|
53
208
|
export interface Logger {
|
|
209
|
+
/**
|
|
210
|
+
* Logs a debug message
|
|
211
|
+
* @param message - The message to log
|
|
212
|
+
* @param args - Additional arguments to log
|
|
213
|
+
*/
|
|
54
214
|
debug: (message: string, ...args: unknown[]) => void;
|
|
215
|
+
/**
|
|
216
|
+
* Logs an info message
|
|
217
|
+
* @param message - The message to log
|
|
218
|
+
* @param args - Additional arguments to log
|
|
219
|
+
*/
|
|
55
220
|
info: (message: string, ...args: unknown[]) => void;
|
|
221
|
+
/**
|
|
222
|
+
* Logs a warning message
|
|
223
|
+
* @param message - The message to log
|
|
224
|
+
* @param args - Additional arguments to log
|
|
225
|
+
*/
|
|
56
226
|
warn: (message: string, ...args: unknown[]) => void;
|
|
227
|
+
/**
|
|
228
|
+
* Logs an error message
|
|
229
|
+
* @param message - The message to log
|
|
230
|
+
* @param args - Additional arguments to log
|
|
231
|
+
*/
|
|
57
232
|
error: (message: string, ...args: unknown[]) => void;
|
|
58
233
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ExtendedWebSocket } from './index';
|
|
2
|
+
export interface NostrWSEvent {
|
|
3
|
+
id: string;
|
|
4
|
+
pubkey: string;
|
|
5
|
+
created_at: number;
|
|
6
|
+
kind: number;
|
|
7
|
+
tags: string[][];
|
|
8
|
+
content: string;
|
|
9
|
+
sig: string;
|
|
10
|
+
}
|
|
11
|
+
export interface NostrWSFilter {
|
|
12
|
+
ids?: string[];
|
|
13
|
+
authors?: string[];
|
|
14
|
+
kinds?: number[];
|
|
15
|
+
'#e'?: string[];
|
|
16
|
+
'#p'?: string[];
|
|
17
|
+
since?: number;
|
|
18
|
+
until?: number;
|
|
19
|
+
limit?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface NostrWSSocket extends ExtendedWebSocket {
|
|
22
|
+
subscriptions?: Set<string>;
|
|
23
|
+
authenticated?: boolean;
|
|
24
|
+
pubkey?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare enum NostrWSMessageType {
|
|
27
|
+
EVENT = "EVENT",
|
|
28
|
+
REQ = "REQ",
|
|
29
|
+
CLOSE = "CLOSE",
|
|
30
|
+
NOTICE = "NOTICE",
|
|
31
|
+
OK = "OK",
|
|
32
|
+
AUTH = "AUTH",
|
|
33
|
+
EOSE = "EOSE"
|
|
34
|
+
}
|
|
35
|
+
export type NostrWSServerMessage = [NostrWSMessageType, ...unknown[]];
|
|
36
|
+
export interface NostrWSServerOptions {
|
|
37
|
+
port: number;
|
|
38
|
+
heartbeatInterval?: number;
|
|
39
|
+
maxPayloadSize?: number;
|
|
40
|
+
cors?: {
|
|
41
|
+
origin: string;
|
|
42
|
+
methods: string[];
|
|
43
|
+
};
|
|
44
|
+
onConnection?: (socket: NostrWSSocket) => void;
|
|
45
|
+
onDisconnect?: (socket: NostrWSSocket) => void;
|
|
46
|
+
onError?: (error: Error, socket?: NostrWSSocket) => void;
|
|
47
|
+
handlers?: {
|
|
48
|
+
message: (socket: NostrWSSocket, message: NostrWSServerMessage) => void | Promise<void>;
|
|
49
|
+
error?: (socket: NostrWSSocket, error: Error) => void;
|
|
50
|
+
close?: (socket: NostrWSSocket) => void;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export var NostrWSMessageType;
|
|
2
|
+
(function (NostrWSMessageType) {
|
|
3
|
+
NostrWSMessageType["EVENT"] = "EVENT";
|
|
4
|
+
NostrWSMessageType["REQ"] = "REQ";
|
|
5
|
+
NostrWSMessageType["CLOSE"] = "CLOSE";
|
|
6
|
+
NostrWSMessageType["NOTICE"] = "NOTICE";
|
|
7
|
+
NostrWSMessageType["OK"] = "OK";
|
|
8
|
+
NostrWSMessageType["AUTH"] = "AUTH";
|
|
9
|
+
NostrWSMessageType["EOSE"] = "EOSE";
|
|
10
|
+
})(NostrWSMessageType || (NostrWSMessageType = {}));
|
package/dist/utils/logger.d.ts
CHANGED
package/dist/utils/logger.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Logger utility for Nostr WebSocket operations
|
|
3
|
+
* @module logger
|
|
4
|
+
*/
|
|
1
5
|
import winston from 'winston';
|
|
6
|
+
/**
|
|
7
|
+
* Creates a logger instance with a specific context
|
|
8
|
+
*
|
|
9
|
+
* @param {string} context - The context identifier for the logger
|
|
10
|
+
* @returns {Logger} A logger instance with debug, info, warn, and error methods
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const logger = getLogger('MyComponent');
|
|
15
|
+
* logger.info('Component initialized');
|
|
16
|
+
* logger.error('An error occurred', new Error('Failed to connect'));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
2
19
|
const logger = winston.createLogger({
|
|
3
20
|
level: process.env.LOG_LEVEL || 'info',
|
|
4
21
|
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
|
|
@@ -8,9 +25,6 @@ const logger = winston.createLogger({
|
|
|
8
25
|
})
|
|
9
26
|
]
|
|
10
27
|
});
|
|
11
|
-
export function getLogger(
|
|
12
|
-
|
|
13
|
-
return logger.child({ service: name });
|
|
14
|
-
}
|
|
15
|
-
return logger;
|
|
28
|
+
export function getLogger(context) {
|
|
29
|
+
return logger.child({ service: context });
|
|
16
30
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nostr-websocket-utils",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Robust WebSocket utilities for Nostr applications with automatic reconnection, channel-based messaging, and type-safe handlers. Features heartbeat monitoring, message queueing, and comprehensive error handling.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"test:coverage": "jest --coverage",
|
|
13
13
|
"lint": "eslint src --ext .ts",
|
|
14
14
|
"prepare": "npm run build",
|
|
15
|
-
"prepublishOnly": "npm test && npm run lint"
|
|
15
|
+
"prepublishOnly": "npm test && npm run lint",
|
|
16
|
+
"docs": "typedoc",
|
|
17
|
+
"docs:watch": "typedoc --watch",
|
|
18
|
+
"predeploy": "npm run build && npm run docs"
|
|
16
19
|
},
|
|
17
20
|
"keywords": [
|
|
18
21
|
"nostr",
|
|
@@ -37,7 +40,7 @@
|
|
|
37
40
|
"bugs": {
|
|
38
41
|
"url": "https://github.com/HumanjavaEnterprises/nostr-websocket-utils/issues"
|
|
39
42
|
},
|
|
40
|
-
"homepage": "https://github.
|
|
43
|
+
"homepage": "https://humanjavaenterprises.github.io/nostr-websocket-utils",
|
|
41
44
|
"dependencies": {
|
|
42
45
|
"@noble/curves": "^1.3.0",
|
|
43
46
|
"@noble/hashes": "^1.3.3",
|
|
@@ -51,6 +54,7 @@
|
|
|
51
54
|
"nostr-tools": "^2.1.4"
|
|
52
55
|
},
|
|
53
56
|
"devDependencies": {
|
|
57
|
+
"@jest/globals": "^29.7.0",
|
|
54
58
|
"@types/jest": "^29.5.14",
|
|
55
59
|
"@types/node": "^20.11.0",
|
|
56
60
|
"@types/ws": "^8.5.10",
|
|
@@ -59,6 +63,8 @@
|
|
|
59
63
|
"eslint": "^8.56.0",
|
|
60
64
|
"jest": "^29.7.0",
|
|
61
65
|
"ts-jest": "^29.1.1",
|
|
66
|
+
"typedoc": "^0.27.5",
|
|
67
|
+
"typedoc-plugin-markdown": "^4.3.2",
|
|
62
68
|
"typescript": "^5.3.3"
|
|
63
69
|
},
|
|
64
70
|
"files": [
|